这是一个计数类的dp
dp[i][j]表示前i个数字中,删除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个元素,那么重复的方案数就是
就是说,要想有重复的序列,一定是能删除完两个相同数字之间的数,也就是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; ll dp[1 << 17][11]; ll a[1 << 17], c[1 << 17], last[1 << 17]; int main() { int n, m, k; while (cin >> n >> m >> k){ memset(last, 0, sizeof last); memset(c, 0, sizeof c); for (int i = 1; i <= n; i++){ cin >> a[i]; last[i] = c[a[i]];///记录前面与当前数字相同的最近位置 c[a[i]] = i;///更新a[i]的新位置 } for (int i = 0; i <= n; i++) dp[i][i] = dp[i][0] = 1;///初始化前i个删除i个,前i个删除0个的方案数 for (int i = 1; i <= n; i++){ for (int j = 1; j <= min(i - 1, m); j++){ dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % mod;///转移 if (last[i] != 0 && i - last[i] <= j){///如果前面有与a[i]相同的数字 并且之间的数字不够删除 dp[i][j] = (dp[i][j] - dp[last[i] - 1][j - (i - last[i])] + mod) % mod;///去重 } } } cout << dp[n][m] << endl; } return 0; }