Removeal

题目链接:https://ac.nowcoder.com/acm/problem/17137

讲解链接:https://blog.nowcoder.net/n/7e7bdb435a0240c9a4cf49926032939d

这是一个计数类的dp
dp[i][j]表示前i个数字中,删除j个元素的方案数
很容易得到转移方程dp[i][j]=dp[i-1][j-1]+dp[i-1][j]dp[i][j]=dp[i1][j1]+dp[i1][j]
意思就是前i个删除j个,要么从前i-1个中删除了j-1个,等于第i个也要删,要么从前i-1个删除了j个,等于第i个不删
这样还没做完,下面考虑去除重复的个数。(其实这就是个容斥的思想,先尽管算,再把重复的去除就是了)
考虑一下怎么会出现重复的情况。
……1,4,7,8,5,1
很容易发现,对于1,4,7,8,5,1这一段,删除1,4,7,8,5 和删除4,7,8,5,1 会造成重复计算。
重复的方案数是多少?其实就是第一个1前面的序列随便选加上后面的1 4 7 8 5 1后再删除1 4 7 8 5 或者4 7 8 5 1
假如是要删除j个元素,那么重复的方案数就是
dp[last[i]-1][j-(i-last[i])] 其中last[i]表示前面与当前a[i]相同的数字的最近位置dp[last[i]1][j(ilast[i])]last[i]a[i]
就是说,要想有重复的序列,一定是能删除完两个相同数字之间的数,也就是i-last[i]个,那么其实就是last[i]-1前面选一部分再加上这个重复的数字
那么因为要删除j个,还需要删除j-(i-last[i])个 肯定就是从last[i]-1前面选择

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const ll MMAX=1e5+7;
ll n,m,k;
ll a[MMAX],last[MMAX],c[MMAX];
ll dp[MMAX][15];
int main()
{
    while(~scanf("%lld%lld%lld",&n,&m,&k))
    {
        memset(last,0,sizeof(last));
        for(int i=1; i<=n; i++)
        {
            scanf("%lld",&a[i]);
            c[i]=last[a[i]]; ///记录前面与当前数字相同的最近位置
            last[a[i]]=i;  ///更新a[i]的新位置
        }
        for(int i=0; i<=n; i++) 
        {                       ///初始化前i个删除i个,前i个删除0个的方案数
            dp[i][0]=1;
            if(i<m) dp[i][i]=1;
            else dp[i][m]=1;
        }
            
        for(ll i=1; i<=n; i++)
        {
            for(ll j=1; j<=min(i-1,m); j++)
            {
                dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod; ///转移
                if(c[i]!=0&&i-c[i]<=j)
                {           ///如果前面有与a[i]相同的数字 并且之间的数字不够删除
                    dp[i][j]=(dp[i][j]-dp[c[i]-1][j-(i-c[i])]+mod)%mod;///去重
                }
            }
        }
        printf("%lld\n",dp[n][m]);
    }

    return 0;
}

 

posted @ 2020-10-13 20:38  小垃圾的日常  阅读(345)  评论(0)    收藏  举报