2018牛客暑假多校一 E(dp)
题目描述:
给你一个长度为n的数列(n<=1e5),数列只会有k(1<=k<=10)种数字。问你如果你可以在这个数列种删除m个数(max(m)=10),问你可以获得的不同的数列的个数为多少。
题目分析:
可以分析,因为在一个长度为n的数列种,删除m个数,倘若只让我们求方案数,那么我们直接可以运用dp[i][j],使得dp代表前i个的数列中删除j个数所形成的方案数,并列出转移方程,dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
但是,因为这个题目中是要统计不相同的数列的个数,而因为倘若前面有与a[i]相同的数字a[k] (k<i),并且i与k的位置距离小于等于j,那么就会产生重复。因此,我们之前所求出的方案数是有很多重复的。
因此,我们还需要去判断当前的数字a[i]在前面是否出现过,倘若在之前出现过a[k],且两者之间的距离正好小于等于可以删除的个数,则我们需要减去这部分重复的方案数。此时的转移方程为 dp[i][j]=dp[i][j]-dp[pre[i]-1][j-(i-pre[i])];
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll a[maxn];
ll dp[maxn][20];
int pre[maxn];
int id[maxn];
int main()
{
int n,m,k;
while(~scanf("%d%d%d",&n,&m,&k)){
memset(pre,0,sizeof(pre));
memset(id,0,sizeof(id));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
pre[i]=id[a[i]];
id[a[i]]=i;
}
for(int i=0;i<=m;i++){
dp[i][i]=1;
}
for(int i=1;i<=n;i++){
dp[i][0]=1;
int d=i-pre[i];
for(int j=1;j<=m;j++){
if(j>i) break;
dp[i][j]=((dp[i-1][j]+dp[i-1][j-1])%mod+mod)%mod;
if(pre[i]!=0&&d<=j){
dp[i][j]=((dp[i][j]-dp[pre[i]-1][j-d])%mod+mod)%mod;
}
}
}
cout<<dp[n][m]<<endl;
}
}