【每日一题】23.Removal (计数DP)

补题链接:Here

计数DP讲解:Here

这是一个计数类的dp
dp[i][j]表示前i个数字中,删除j个元素的方案数

很容易得到转移方程:\(f[i][j] = f[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]\) 相同数字的最近位置

就是说,要想有重复的序列,一定是能删除完两个相同数字之间的数,也就是i-last[i]个,那么其实就是last[i]-1前面选一部分再加上这个重复的数字
那么因为要删除j个,还需要删除j-(i-last[i])个 肯定就是从last[i]-1前面选择

// RioTian 21/05/11
#include <bits/stdc++.h>
using namespace std;
using ll     = long long;
const ll mod = 1e9 + 7;
int n, m, k;
ll dp[1 << 17][11];
ll a[1 << 17], c[1 << 17], last[1 << 17];
void solve() {
    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;
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    while (cin >> n >> m >> k) solve();
    return 0;
}
posted @ 2021-05-11 14:49  Koshkaaa  阅读(62)  评论(0编辑  收藏  举报