Codeforces #638 div2 A - F 题解

参考资料:https://codeforces.ml/blog/entry/76555

 

 A. Phoenix and Balance

 

  $Description:$

    有一个为$2^1, 2^2, 2^3......2^n$的数列,$n$是偶数,将数列平均分为两个,两边各有$\frac{n}{2}$个数,问两个数列之和的差最小是多少?

  $Solve:$

    由等比数列求和我们知道$2^1+2^2+2^3+...+2^{n-1} = 2^n-2$

    所以$2^n$是大于前面所有的数的和的,所以我们贪心的将前$\frac{n}{2}-1$个数和$2^n$分配到一起就是最优解了。

  $Code:$

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 35;
 4 typedef long long ll;
 5 
 6 ll f[N];
 7 ll sum[N];
 8 
 9 void init()
10 {
11     f[0] = 1;
12     for(int i = 1; i <= 30; i ++)
13         f[i] = f[i - 1] << 1;
14     sum[0] = 0;
15     for(int i = 1; i <= 30; i ++)
16         sum[i] = sum[i - 1] + f[i];
17 }
18 
19 int main()
20 {
21     init();
22     int t; cin >> t;
23     while(t --)
24     {
25         int n; cin >> n;
26         ll a = f[n] + sum[n / 2 - 1]; // 最后一项和前 n / 2 - 1 项
27         ll b = sum[n] - a;
28         cout << abs(a - b) << endl;
29     }
30     return 0;
31 }

 

B. Phoenix and Beauty

  $Description:$

    有$n$个数,大小都在$[1,n]$中,你可以插入$m$个数(任意位置)以使新得到的数列满足以$k$为长度的连续子序列的和相等,不要求$m$最小,但$n + m \leq 10000$,插入的数要求在$[1,n]$之间。$k\leq n\leq 100$。

  $Solve:$

    根据数据范围的提示,我们就应该用暴力。但是如果我们真的按照他的要求,根据子序列的和相等来做就复杂了。

    对于和相等我们可以转变为子序列里的数完全相同。

    例如我们有这样一个数列,假设$k = 4$:

    $a_1, a_2, a_3, a_4, a_1, a_2, a_3, a_4$;

    显然长度为$4$的子序列的和是一定相等的。

    所以我们可以来构造一个周期为$k$的数列。对于原数列的每一个数都插入$k-1$个数,即可符合要求。

    显然当原数列中不同数的个数$>k$的时候,不能满足题意。

  $Code:$

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 int main()
 5 {
 6     int t; cin >> t;
 7     while(t --)
 8     {
 9         int n, k;
10         cin >> n >> k;
11         vector<int> vec; // 存不同的数
12         map<int, int> m; // 记录某数是否出现
13         for(int i = 1; i <= n; i ++)
14         {
15             int num; cin >> num;
16             if(m[num] == 1) continue; // 如果出现就跳过
17             m[num] = 1;
18             vec.push_back(num);
19         }
20         int lenVec = vec.size();
21         // 不同数的个数 > k
22         if(lenVec > k) { puts("-1"); continue; }
23         
24         // 插入一些数以使 lenVec = k
25         for(int i = lenVec; i < k; i ++)
26         {
27             for(int j = 1; j <= n; j ++)
28                 if(m[j] == 0) // 该数未出现则可以插入
29                 {
30                     vec.push_back(j);
31                     m[j] = 1;
32                     break;
33                 }
34         }
35         cout << n * k << endl;
36         for(int i = 1; i <= n; i ++)
37             for(int j = 0; j < k; j ++)
38                 printf("%d ", vec[j]);
39         puts("");
40     }
41     return 0;
42 }

 

 

C. Phoenix and Distribution

  $Description:$

    有一个字符串$S$,要将其分配到$k$个空串上,要求最后$k$个字符串都不为空串,在每一种分配方案中都可以得到一个字典序最大的字符串,那么在其中最小的是什么?

  $Solve:$

    先将$S$排序,然后将前$k$个字符分配出去,那么将出现两种情况

      $1:s[k]= s[0]$ 

      $2:s[k]> s[0]$ 

    对于第二种情况,答案显然即为$s[k]$

    对于第一种情况,我们再看剩下的$s[k+1]到s[n]$这些字符是否都相同,

      如果都相同 ,那么平均分之后取最长的即为答案

      如果不相同,那么将其全部接在$s[k]$之后即可

  $Code:$

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5 + 10;
 4 
 5 int main()
 6 {
 7     int t; cin >> t;
 8     while(t --)
 9     {
10         char s[N];
11         char ans[N];
12         int n, k;
13         cin >> n >> k;
14         scanf("%s", s + 1);
15         sort(s + 1, s + n + 1);
16         if(s[k] != s[1]) { printf("%c\n", s[k]); continue; }
17         int idx = 0;
18         ans[idx ++] = s[1];
19         // 由于是排序后的,所以可以直接判断首尾是否相同
20         if(s[k + 1] == s[n]) 
21         {
22             int cnt = n - k;
23             if(cnt % k == 0) cnt /= k;
24             else cnt = cnt / k + 1;
25             for(int i = 0; i < cnt; i ++)
26                 ans[idx ++] = s[n];
27         }
28         else
29         {
30             for(int i = k + 1; i <= n; i ++)
31                 ans[idx ++] = s[i];
32         }
33         ans[idx] = '\0';
34         printf("%s\n", ans);
35         //printf("s = %s\n", s + 1);
36         //printf("---------------------\n");
37     }
38     return 0;
39 }

 

D. Phoenix and Science

  $Description:$

    开局一个细菌,重量为$1$, 在每天早上,细菌可以选择是否分裂为两个细菌,重量平均分,而在每天晚上,每个细菌的重量都会$+1$,那么在最短的时间内如何让你的所有的细菌的重量为$n$? 

  $Solve:$

    首先要知道我们一定可以找到一种方法,最不济就每天不分裂,耗时$n-1$天。

    那么要在最短的时间,我们就要贪心的来最大化每天可以增加的重量每天都全部分裂

    那么增量为:$2^1,2^2,2^3,2^4......2^t$,显然我们可以找到一个$t$,使得$2^1+2^2+...+2^t+2^{t+1}\geq n$;

    显然我们需要分裂$t+1$天(但是太多了),设$sum = 2^1+2^2+...+2^t$;则$cnt = n - sum$,也就是我们全力增加了$t$天后还是不够,还需要增加$cnt$。

    也就是说我们需要在$[1,t]$天中来插入一天且该天的增量为$cnt$,由于每天的增量就是每天的细菌数的个数,且细菌数都是积少成多的,所以我们只需要对增量排个序即可。

  $Code:$  

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 35;
 4 typedef long long ll;
 5 
 6 int main()
 7 {
 8     int t; cin >> t;
 9     while(t --)
10     {
11         int n; cin >> n;
12         vector<int> vec;
13         for(int i = 1; i <= n; i <<= 1)
14         {
15             vec.push_back(i);
16             n -= i;
17         }
18         if(n) vec.push_back(n);
19         sort(vec.begin(), vec.end());
20         cout << vec.size() - 1 << endl;
21         for(int i = 1; i < vec.size(); i ++)
22             cout << vec[i] - vec[i - 1] << " ";
23         puts("");
24     }
25     return 0;
26 }

 

E. Phoenix and Berries

  $Description:$

    有$n$棵树,每个树上有蓝色和红色的果子,你有数不清的篮子,每个篮子的容量都为$k$,篮子装果子有两种装法:

      $1.$只能装同色的果子

      $2.$只能装同树的果子

    问必须在篮子都装满的情况下,你最多可以用多少个篮子?

  $Solve:$

    首先设$sumA$ 为红果子的数量和,$sumB$为蓝果子的数量和

    那么在只考虑同色的情况下$ans = sumA/k + sunB/k$,

    但是如果$sumA$%$k+ sumB$%$k\geq k$,即我们还有可能在凑出一篮,显然是同树的情况了。

    考虑用DP,设$f[i][j]$为在第$i$树的情况下,且剩余的红果的数量为$j$时,最大的篮子数。

    那么由于我们要在同树的情况下做出选择,即在这颗树时,我们考虑同树的做法,并拿出$s$个红果,则还需要$k-s$个蓝果。

    那么当前的红果数$la = j + a[i] - s$

        当前的蓝果数$lb = sum - f[i-1][j] * k - la - k$($sum$为前$i$颗树上的果子总数)

    那么就可以得出转移方程:$f[i][la$%$k] = max(f[i][la$%$k],f[i-1][j]+la/k+lb/k+1)$。

    对于这棵树如果没有同树的选择:

      那么 $la = j + a[i]$

         $lb = sum - f[i-1][j] * k - la$

      $f[i][la$%$k]=max(f[i][la$%$k],f[i-1][j]+la/k+lb/k)$。

  $Code:$

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 510;
 4 typedef long long ll;
 5  
 6 ll f[N][N];
 7  
 8 int main()
 9 {
10     int n, k, a, b;
11     scanf("%d%d", &n, &k);
12     memset(f, -1, sizeof f); // 为-1就表示当前的状态不合法
13 
14     f[0][0] = 0;
15     ll sum = 0;
16     for(int i = 1; i <= n; i ++)
17     {   
18         scanf("%d%d", &a, &b);
19         sum += a + b;
20         for(int j = 0; j < k; j ++)
21         {
22             if(f[i - 1][j] < 0) continue; // 不合法的状态
23             int la, lb;
24             for(int s = 1; s < k && s <= a; s ++)
25             {
26                 if(k - s > b) continue; // 蓝果子不够同树
27                 la = j + a - s;
28                 lb = sum - f[i - 1][j] * k - la - k;
29                 f[i][la % k] = max(f[i][la % k], f[i - 1][j] + la / k + lb / k + 1);
30             }
31             la = j + a;
32             lb = sum - f[i - 1][j] * k - la;
33             f[i][la % k] = max(f[i][la % k], f[i - 1][j] + la / k + lb / k);
34         }
35     }
36  
37     ll ans = 0;
38     for(int i = 0; i < k; i ++)
39         ans = max(ans, f[n][i]);
40     cout << ans << endl;
41  
42     return 0;
43 }

 

F. Phoenix and Memory

  $Description:$

    有$n$个位置,每个位置上的人都有一个$ID[1,n]$,每个位置上都有一个关乎他的$ID$的区间信息,即他的$ID$在这个区间中,那么对于如上已知的信息,你有几种解法可以得出每个人的$ID$,符合条件。如果有多种解法,输出其中任意的两种。

  $Solve:$

    我们可以简单贪心的得到一种符合条件的方案。

      可以将每个区间按左端点排序,然后对于每个$ID$,找到左端点大于等于他且右端点最小的即可。

    麻烦的是如何确定有多种解。

    首先我们要知道,在原有解的基础上,有两个位置的人可以互换$ID$,那么显然不止一种解,我们将其的$ID$互换即可做出该题。

    设这两个人的位置为$i, j$,他们的$ID$分别为$P_i, P_j$,由可以互换$ID$

      那么:$L_i\leq P_j\leq R_i$,$L_j\leq P_i\leq R_j$,

      我们假设$P_i<P_j$,则:$L_j\leq P_i\leq P_j\leq R_i$,

      我们去枚举$P_i$,那么已知的信息有$R_i$,即我们在区间$[P_i+1,R_i]$去找一个$ID$为$P_j$,要满足$L_j\leq P_i$;

      显然是让我们在区间找最小值,用线段树可解。

    详情见代码。

  $Code:$

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 2e5 + 10, INF = 1e9 + 10;
 4 typedef pair<int, int> PII;
 5 
 6 int n;
 7 int a[N], b[N];
 8 int ans[N], pos[N];
 9 PII tree[N << 2]; // (左端点,位置)
10 vector<PII> vec[N];
11 
12 // 建树
13 void build(int node, int l, int r){
14     if(l == r){
15         tree[node].first = a[pos[l]];
16         tree[node].second = l;
17         return;
18     }
19     int mid = l + r >> 1;
20     build(node << 1, l, mid);
21     build(node << 1 | 1, mid + 1, r);
22     tree[node] = min(tree[node << 1], tree[node << 1 | 1]);
23 }
24 
25 // 查询区间[L, R]中的最小值
26 PII query(int node, int l, int r, int L, int R){
27     if(L <= l && R >= r) return tree[node];
28     int mid = l + r >> 1;
29     PII res1 = {INF, INF};
30     PII res2 = {INF, INF};
31     if(L <= mid) res1 = query(node << 1, l, mid, L, R);
32     if(R >  mid) res2 = query(node << 1 | 1, mid + 1, r, L, R);
33     return min(res1, res2);
34 }
35 
36 void print(){
37     for(int i = 1; i <= n; i ++){
38         printf("%d ", ans[i]);
39     }
40     puts("");
41 }
42 
43 int main(){
44     cin >> n;
45     for(int i = 1; i <= n; i ++){
46         scanf("%d%d", &a[i], &b[i]);
47         vec[a[i]].push_back({b[i], i}); 
48     }
49     set<PII> s;
50     for(int i = 1; i <= n; i ++){ // 枚举编号
51         s.insert(vec[i].begin(), vec[i].end()); // 插入左端点>=编号的区间
52         ans[s.begin()->second] = i; // 位置为s.begin()->second的人的编号是i
53         pos[i] = s.begin()->second; // 编号为i的人的位置是s.begin()->second
54         s.erase(s.begin()); // 已经用过的区间删除掉
55     }
56 
57     build(1, 1, n);
58     for(int i = 1; i <= n; i ++){ // 枚举编号
59         PII tmp = query(1, 1, n, i + 1, b[pos[i]]);
60         if(tmp.first <= i){ // 左端点 <= 编号,即 L_j <= P_i
61             puts("NO");
62             print();
63             swap(ans[pos[i]], ans[pos[tmp.second]]); // 交换
64             print();
65             return 0;
66         }
67     }
68 
69     puts("YES");
70     print();
71 
72     return 0;
73 }
posted @ 2020-05-05 16:09  nonameless  阅读(229)  评论(0编辑  收藏  举报