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;
}