題解:CF645E Intellectual Inquiry

hhhqx發表於2024-12-03

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;
}