2018牛客多校第一场

E.Removal

题意:给一个长度为n的数字序列,每个数字大小都不超过k,要删除其中m个数字问有多少种删除后的序列不相同的方案。

题解:DP。设dp[i][j]表示从1~i 删除j个数字的方案有多少种。首先初始化,很容易想到dp[i][0]=1,同时dp[i][i]=1;

转移方程是:dp[i][j]=dp[i-1][j]+dp[i-1][j-1] (前i-1个数字中删除j-1个再删除第i个数字 + 前i-1个中删除j个 );这里要注意的是,如果前面就出现了与当前的a[i]相同的a[k],并且i和k的距离小于等于j,那么当前删除j个数字的方案中就会有一些重复的。举个例子:考虑在序列…, 1, 2, 3, 1, 2中删除3个数字,(…,xxx, 1, 2)和(…, 1, 2)是一样的,也就是说删除两个相同数字之间的所有数字+这两个数字中的一个会有一次重复计数,需要减去,在例子中(…, 1, 2)被计数了两次,要减去一次(即减去(…)的次数)。我们可以用一个数组pre[] 记录每个数字在序列中上一次出现的位置,则dp[i][j]应该减去dp[pre[i]-1][j-(i-pre[i])];

附上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int mod=1000000007;
int dp[maxn][11];
int a[maxn],pre[maxn],pos[maxn];
int main()
{
    int n,m,k;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        memset(pos,0,sizeof(pos));
        memset(dp,0,sizeof(dp));
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            pre[i]=pos[a[i]];
            pos[a[i]]=i;
        }
        for(int i=0;i<=n;i++)
            dp[i][i]=dp[i][0]=1;
     for(int i=1;i<=n;i++){
            int v=i-pre[i];
            for(int j=1;j<=m&&j<i;j++)
     {
         dp[i][j]=((dp[i-1][j]+dp[i-1][j-1])%mod+mod)%mod;
         if(pre[i]&&v<=j)
         dp[i][j]=((dp[i][j]-dp[pre[i]-1][j-v])%mod+mod)%mod;
     }
     }
     printf("%d\n",dp[n][m]);
    }
return 0;
}

猜你喜欢

转载自blog.csdn.net/wookaikaiko/article/details/81142347