题面 : https://codeforc.es/contest/1183
题目大意是,一个长为n(1<=n<=100)的字符串 s 的k(k在Easy Version中是 1~100,在Hard Version中是1~1e12,两种版本就此处不同)个子序列组成一个集合,得到这个集合会产生一个代价,该代价是集合中每个元素的长度与原串s的长度之差的总和,现在求这个代价最小的值,如果字符串s没有k个子序列则输出"-1"。
初看此题,应当知道子序列不可能绝对是 2^n个,因为会有重复的子序列。
对于 Easy Version 我们可以模拟从原串一个个生成子序列,存储子序列的集合最大为100,所以复杂度没问题。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 queue< string > que; 5 set< string > S; 6 7 int main(){ 8 ios::sync_with_stdio(0);cin.tie(0); 9 string str; 10 int N,K; 11 int ans = 0; 12 cin >> N >> K; 13 cin >> str; 14 que.push(str); 15 S.insert(str); 16 while(!que.empty()&&S.size()<K){ 17 string cur = que.front(); 18 que.pop(); 19 int len = cur.length(); 20 for(int i = 0; i<len; ++i){ 21 string test = cur; 22 test.erase(i,1); 23 if(S.size()<K&&S.count(test)==0){ 24 que.push(test); 25 S.insert(test); 26 ans += N-len+1; 27 } 28 } 29 } 30 cout << (S.size()==K?ans:-1); 31 return 0; 32 }
对于 Hard Version ,由于K可以到达1e12,显然搜索超时。
先来看一看另外一个简单的问题,如果不考虑子序列长度,一个给定的字符串我们如何得知其所有子序列个数呢?假设从字符串str长度为len,下标从1~len,设f[i]为考虑前 i 个字符的答案,那么
f[i] = f[i-1]*2 ; (第 i 个字符从未出现)
f[i] = f[i-1]*2 - f[last[str[i]]-1] ; (第 i 个字符出现过,last[]为记录该字符上一次出现的位置)
"*2"很显然表示的是添加这个字符或不加,然而有重复的子序列在其中,需要减去这个字符上一次作为结尾的序列,"-1"便是该字符上一个位置之前的所有子序列并加上它结尾,没有"-1”则多减去不以它结尾的子序列。
好的,了解这个经典的问题后,我们再来解决这个 hard version ,现在我们给dp的状态额外加一个维度表示子序列长度,f[i][j] 即是考虑前 i 个字符,长为 j 的子序列。
f[i][j] = f[i-1][j-1] + f[i-1][j] ; (第 i 个字符从未出现)
f[i][j] = f[i-1][j-1] + f[i-1][j] - f[last[str[i]]-1][j-1] ; (第 i 个字符出现过,last[]为记录该字符上一次出现的位置)
答案就从 f[len][len ~ 0] 里找前 K 个进行计算,BTW为了防止数字过大溢出,在转移的时候加一个 min (K , f[i][j]); 这样可防止溢出并保证答案正确 。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 6 ll f[123][123]; 7 int last[26]; 8 int main(){ 9 ios::sync_with_stdio(0); 10 int N; 11 ll K; 12 cin >> N >> K; 13 string str; 14 cin >> str; 15 f[0][0] = 1; 16 for(int i=1;i<=N;++i){ 17 f[i][0] = 1; 18 for(int j=1;j<=i;++j){ 19 if(last[str[i-1]-'a']) 20 f[i][j] = min(K , f[i-1][j-1] + f[i-1][j] 21 - f[last[str[i-1]-'a']-1][j-1] ); 22 else 23 f[i][j] = min(K , f[i-1][j-1] + f[i-1][j]); 24 } 25 last[str[i-1]-'a'] = i; 26 } 27 ll ans = 0; 28 for(int i=N;i>=0;i--){ 29 ll cur = min(f[N][i],K); 30 //cout << "f["<< N <<"]["<<i<<"]"<<f[N][i] << endl; 31 ans += cur * (N-i); 32 K -= cur; 33 34 } 35 cout << (K==0?ans:-1); 36 return 0; 37 }
另外还有一个dp的方法,状态用 dp[i][ch] 表示长度为 i 以字符 ch 结尾的子序列个数。有空再写。