Codeforces #638 div2 A - F 题解
$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 }
$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 }
$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 }
$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 }
$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 }
$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 }