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;
    }
}
原文地址:https://www.cnblogs.com/Chen-Jr/p/11007267.html