E. Subsequences (easy version)
本题和H题唯一的不同点是数据范围。
你有一个长度为nn的字符串。你可以选择它的任意一个子序列。子序列定义为可以将这个字符串删去若干个字符得到。特别的,空串也是一个子序列。
对于一个长度为mm的子序列,选出它的花费是n-mn−m,也就是你需要删掉的字符数量。
你的任务是选出来kk个本质不同的子序列,使得总花费最小。输出这个最小花费。
如果选不出kk个,输出-1−1。
思路:
因为本题的数据量比较小,所以我们可以采取BFS去模拟整个的过程。
利用bfs与set结合,队列存储当前字符串,每次删除一个字符,若set不存在,更新队列与set。
bfs先从删除少的字符串开始,保证了代价最小效率最优。

1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 #include <algorithm>
5 #include <set>
6 #include <queue>
7
8 #define LL long long
9 using namespace std;
10 const int MAXN = 200005;
11
12 int n,k;
13 set<string> st;
14 queue<string> q;
15 string s;
16
17 int main()
18 {
19 scanf("%d%d",&n,&k);
20 cin >> s;
21 int cnt = 0;
22 q.push(s);
23 st.insert(s);
24 while (!q.empty() && st.size()<k)
25 {
26 string v = q.front();
27 q.pop();
28 for (int i=0;i<v.size();i++)
29 {
30 string nv = v;
31 nv.erase(i,1);
32 if (!st.count(nv) && st.size()+1<=k)
33 {
34 q.push(nv);
35 st.insert(nv);
36 cnt += (n-nv.size());
37 }
38 }
39 }
40 if (st.size()<k)
41 printf("-1\n");
42 else
43 printf("%d\n",cnt);
44 return 0;
45 }
F. Topforces Strikes Back
从n个数中选出来最多3个数,使得这三个数中不存在任意一个数是另一个数的倍数,同时使得这三个数的和最大。
这道题的贪心的想法非常巧妙,看了大佬的博客才知道怎么去贪。 Orz
我们根据我们选出的个数进行讨论:
一、
如果我们只选出了一个数,那么肯定就直接选最大的
二、
如果我们选出了两个数,我们讨论下是不是还一定要选最大的数a呢?答案是肯定的
证明如下:
1、如果有一个数不是a的因数,那么直接用a替换这个数。
2、如果两个数都是a的因数,那么这两个数的和最大是 a/2+a/3 < a,也就是说我们在步骤一中就已经得到最优解了。
所以对于这一种情况,我们只需要选出来最大的数,然后从剩下的数中找到一个不是它的因数的最大的数即可。
三、
如果选出三个数,我们同样还是来看是否一定要选a
可以发现,如果选出来的三个数中有一个或两个数不是a的因数,那么就可以由情况二进行证明,一定要选出来a。
而如果三个数都是a的因数,我们会发现唯一一种特殊情况 a/2 + a/3 + a/5 > a ,我们只要对这种情况进行一次特判就可以了。特判完后就选出最大的a然后一样用第二种情况的方法进行判断

1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 #include <algorithm>
5 #include <set>
6 #include <queue>
7
8 #define LL long long
9 using namespace std;
10 int t,n,a[200005];
11 int main() {
12 // freopen("../in.txt","r",stdin);
13 scanf("%d", &t);
14 while (t--) {
15 scanf("%d", &n);
16 int ans = 0;
17 for (int i = 1; i <= n; i++) {
18 scanf("%d", &a[i]);
19 ans = max(ans, a[i]);
20 }
21 sort(a + 1, a + n + 1);
22 int an = a[n];
23 bool flag1 = 0, flag2 = 0, flag3 = 0;
24 for (int i = 1; i <= n; i++) {
25 if (a[i] * 2 == an)flag1 = 1;
26 if (a[i] * 3 == an)flag2 = 1;
27 if (a[i] * 5 == an)flag3 = 1;
28 }
29 if (flag1 && flag2 && flag3)ans = max(ans, an / 2 + an / 3 + an / 5);
30 for (int i=1;i<=n;i++){
31 if (a[n]%a[i] == 0){
32 a[i] = 2000000000;
33 }
34 }
35 sort(a+1,a+n+1);
36 while (a[n] == 2000000000){
37 n--;
38 }
39 ans = max(ans,an+a[n]);
40 for (int i=1;i<=n;i++){
41 if (a[n]%a[i] != 0){
42 ans = max(ans,an+a[n]+a[i]);
43 }
44 }
45 printf("%d\n",ans);
46 }
47 return 0;
48 }
H. Subsequences (hard version)
本题和E题唯一的不同点是数据范围。
你有一个长度为n的字符串。你可以选择它的任意一个子序列。子序列定义为可以将这个字符串删去若干个字符得到。特别的,空串也是一个子序列。
对于一个长度为m的子序列,选出它的花费是n−m,也就是你需要删掉的字符数量。
你的任务是选出来kk个本质不同的子序列,使得总花费最小。输出这个最小花费。
如果选不出k个,输出-1。
思路:
这道题的数据范围比E题高了许多,所以如果跟E一样去搜索的话会超时。
这题是个dp题
确定状态:我们不妨设 dp[i][j] 的含义是前 i 个 长度为 j 的字符串的子序列的个数
确定状态转移方程:
第一、如果字符str[i] 第一次出现 dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
(取str[i]) (不取str[i])
第二、如果字符str[i] 不是第一次出现 那么我们就找它前面出现的时候 vis[str[i]-'a']
dp[i][j] = dp[i-1][j-1] + dp[i-1][j] - dp[str[i]-'a'-1][j-1] (减去重复的就好了)
我的代码采取了一个优化,就是如果此时序列已经大于k了我就直接取k就好了

1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 #include <algorithm>
5 #include <set>
6 #include <queue>
7
8 #define ll long long
9 using namespace std;
10 const int MAXN = 105;
11
12 ll n,k;
13 char s[110];
14 ll dp[MAXN][MAXN];
15 int vis[28];
16 int main()
17 {
18 scanf("%lld%lld",&n,&k);
19 scanf("%s",s+1);
20 dp[0][0]=1;
21 for(int i=1;i<=n;i++)
22 {
23 int alp=s[i]-'a';
24 dp[i][0]=1;
25 for(int j=1;j<=i;j++)
26 {
27 dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
28 if(vis[alp])
29 {
30 dp[i][j]-=dp[vis[alp]-1][j-1];
31 }
32 dp[i][j]=min(dp[i][j],k);
33 }
34 vis[alp]=i;
35 }
36 ll cost=0;
37 for(int i=n;i>=0;i--)
38 {
39 cost+=min(dp[n][i],k)*(n-i);
40 k-=min(dp[n][i],k);
41 if(k==0) break;
42 }
43 if(k>0) printf("-1\n");
44 else printf("%lld\n",cost);
45 return 0;
46 }
47
来源:https://www.cnblogs.com/-Ackerman/p/11236089.html
