加载中...

本质不同子序列

对于本质不同子序列类型题,可以用 dp 解决,关键在于去重。

2025江苏省赛H

状态定义

\(dp_{i}\):考虑前 \(i\) 个字符,必须以 \(i\) 位置字符 \(s_{i}\) 结尾、满足要求的本质不同子序列数量。
\(f_{i,j}\):考虑前 \(i\) 个字符,以字符 \(j\) 结尾、满足要求的本质不同子序列数量。
(以上“满足要求”指的是题目对于子序列的约束条件:子序列中任意两个相邻字符在原串中的位置相距必须 \(> k\)

状态转移

void solve()
{
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    s = " " + s;
    vector<vector<ll>> f(n + 1, vector<ll>(26, 0));
    vector<ll> dp(n + 1, 0);
    dp[0] = 1;
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j < 26; j ++){
            dp[i] = (dp[i] + f[max(0, i - k - 1)][j]) % mod;
        }
        dp[i] = (dp[i] + 1) % mod; // 前面为空,只算自己也是一种方案
        for(int j = 0; j < 26; j ++){
            if(j == s[i] - 'a'){
                f[i][j] = dp[i]; // 关键转移,也是去重部分所在,可以自己细细体会
            }
            else{
                f[i][j] = f[i - 1][j];
            }
        }
    }
    ll ans = 0;
    for(int j = 0; j < 26; j ++){
        ans = (ans + f[n][j]) % mod;
    }
    cout << ans << "\n";
}

ABC446G

与上一题的区别在于:

  • 字符集大小巨大
  • 子序列的形式有固定要求
const int mod = 998244353;
int n;
int a[500005];
vector<int> pos[500005];
ll f[500005]; // f[i]:考虑前i个字符,满足要求的本质不同子序列数量
ll dp[500005]; // dp[i]:考虑前i个字符,必须以i位置数字结尾、满足要求的本质不同子序列数量

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
        pos[i].pb(0);
    }
    for(int i = 1; i <= n; i ++){
        int v = a[i];
        pos[v].pb(i);
        int cnt = (int)pos[v].size() - 1;
        if(cnt >= v){ // 以前 i 个字符中,最后 v 个 v 结尾的子序列
            dp[i] = (f[pos[v][cnt - v + 1] - 1] + 1) % mod;
        }
        if(cnt >= v + 1){ // 需要注意以 v 个 v 结尾后,前面不能再承接 v 个 v 结尾
            dp[i] = (dp[i] - dp[pos[v][cnt - v]] + mod) % mod;
        }
        f[i] = (f[i - 1] + dp[i]) % mod;
        if(cnt >= 2){ // 对于仅考虑前 i - 1 个字符结尾的所有情况中,以 v 个 v 结尾的情况会重复计算
            f[i] = (f[i] - dp[pos[v][cnt - 1]] + mod) % mod;
        }
    }
    cout << f[n] << "\n";
}
posted @ 2026-04-26 12:04  小橘奏  阅读(23)  评论(0)    收藏  举报