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[i−1][j−1]+dp[i−1][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−(i−last[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; }