Codeforces Round #570 (Div. 3) Subsequences (Easy and Hard Version)

题面 : 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 }
BFS

 

对于 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 }
dpNN

 

另外还有一个dp的方法,状态用 dp[i][ch] 表示长度为 i 以字符 ch 结尾的子序列个数。有空再写。

posted on 2019-07-21 20:31  Emiya_Kiritsugu  阅读(130)  评论(0编辑  收藏  举报

导航