https://www.luogu.com.cn/problem/CF645E
走路題,第一步顯然是想:如何統計不同子序列數量。
- 考慮 dp。設 \(f_{i}\) 表示前 \(i\) 個字元有多少不同子序列。如果直接 \(f_i = 2 \times f_{i-1}\),會算重複,考慮哪裡會算重複?
- 當前字元是 \(c\),則前面所有以 \(c\) 結尾的的子序列都會重算一遍。
- 設 \(g_{c}\) 表示字元 \(c\) 結尾的不同子序列數量。設當前字元是 \(c\),則 \(g_c = \sum\limits_{i=0}^{k-1}{g_c}\)。
- 但是這個轉移式子忽略了空串的情況,所以應該是新增 \(g_k\) 表示空串結尾的數量。
- \(g_k\) 初始值設為 \(1\),然後 \(g_c = \sum\limits_{i=0}^{k}{g_c}\)(\(c\) 不能等於 \(k\)) 推下去就可以了。
觀察這個轉移,如果可以選則 \(c\),則每次選最小的 \(g_c\) 去轉移最優。
還發現每經過一次轉移 \(g_c\) 又會變為最大值。
然後就可以直接做了。
考慮離散化,每次轉移 \(g_c\),只需要把 \(g_c\) 的離散值設為當前離散值中最大的就可以了。
具體實現可以看程式碼,應該還是很好懂的。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MAXN = 3e6 + 3;
const int mod = 1e9 + 7;
int n, m, k, dp[27];
string s;
int tot = 0, mp[MAXN], w[27];
set<int> st;
void ADD(int &x, int y){ x = (x + y) % mod; }
void Update(int i, int c){
for(int j = 0; j <= k; j++){
if(j != c) ADD(dp[c], dp[j]);
}
st.erase(w[c]);
tot++, mp[tot] = c, w[c] = tot;
st.insert(tot);
}
int main(){
cin >> n >> k >> s, m = s.size(), s = " " + s;
tot = k;
for(int i = 0; i < k; i++) st.insert(i), mp[i] = i, w[i] = i;
dp[k] = 1;
for(int i = 1; i <= m; i++){ int c = s[i] - 'a';
Update(i, c);
}
for(int i = m + 1; i <= m + n; i++){
int c = mp[*st.begin()];
Update(i, c);
}
int ans = 0;
for(int j = 0; j <= k; j++) ADD(ans, dp[j]);
cout << ans;
return 0;
}