本质不同子序列
对于本质不同子序列类型题,可以用 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";
}

浙公网安备 33010602011771号