A.Alice and Books(思维)
题意:
爱丽丝有nnn本书。第111本书包含a1a_1a1页,第222本书包含a2a_2a2页,…\ldots…第nnn本书包含ana_nan页。爱丽丝的操作如下:
- 她把所有的书分成两个非空的书堆。这样,每本书最后都会被恰好放在两堆书中的一堆里。
- 爱丽丝阅读每一堆中编号最高的一本书。
爱丽丝非常喜欢阅读。帮她找出把书分成两堆后,她最多可以阅读的总页数。
分析:
观察题目很容易看出来,最后一个数字是必须取的,用前n−1n-1n−1个数字的最大值加上最后一个值求和即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int ans = a[n];
int ans2 = 0;
for (int i = 1; i < n; ++i) {
ans2 = max(ans2, a[i]);
}
cout << ans + ans2 << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
B.New Bakery(数学)
题意:
鲍勃决定开一家馒头店。开业当天,他烤出了nnn个可以出售的馒头。通常一个馒头的价格是aaa个硬币,但为了吸引顾客,鲍勃组织了以下促销活动:
- 鲍勃选择某个整数kkk(0≤k≤min(n,b)0\le k\le\min(n,b)0≤k≤min(n,b))。
- 鲍勃以修改后的价格出售第一批kkk个馒头。在这种情况下,售出的iii(1≤i≤k1\le i\le k1≤i≤k)个馒头的价格是(b−i+1)(b-i+1)(b−i+1)个硬币。
- 剩下的(n−k)(n-k)(n−k)个馒头以每个aaa个硬币的价格出售。
注意kkk可以等于000。在这种情况下,鲍勃将以每个aaa硬币的价格出售所有的馒头。
帮助鲍勃确定出售所有nnn个馒头所能获得的最大利润。
分析:
首先当a≥ba≥ba≥b时,kkk取000即可。
当a<ba\lt ba<b时,要想使利润尽可能大,必须满足前kkk天的利润都比aaa大,即要满足b−k≥ab−k≥ab−k≥a。可以得出kkk最大为b−ab−ab−a,然后计算即可。
注意kkk需要满足不超过min(n,b)min(n,b)min(n,b)的限制,所以最终k=min(n,b−a)k=min(n,b−a)k=min(n,b−a),答案为(b+b−k+1)k2+(n−k)a\frac{(b+b−k+1)k}{2}+(n−k)a2(b+b−k+1)k+(n−k)a。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
LL n, a, b;
cin >> n >> a >> b;
if (a >= b) {
cout << n * a << endl;
return;
}
LL k = min(n, b - a);
cout << (b + b - k + 1) * k / 2 + a * (n - k) << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
C.Manhattan Permutations(构造)
题意:
让我们把排列†^{\dagger}†的曼哈顿值ppp表示为∣p1−1∣+∣p2−2∣+…+∣pn−n∣|p_1-1|+|p_2-2|+\ldots+|p_n-n|∣p1−1∣+∣p2−2∣+…+∣pn−n∣的值。
例如,对于排列[1,2,3][1,2,3][1,2,3],曼哈顿值为∣1−1∣+∣2−2∣+∣3−3∣=0|1-1|+|2-2|+|3-3|=0∣1−1∣+∣2−2∣+∣3−3∣=0,而对于排列[3,1,2][3,1,2][3,1,2],曼哈顿值为∣3−1∣+∣1−2∣+∣2−3∣=2+1+1=4|3-1|+|1-2|+|2-3|=2+1+1=4∣3−1∣+∣1−2∣+∣2−3∣=2+1+1=4。
给你整数nnn和kkk。请找出长度为nnn的排列ppp使其曼哈顿值等于kkk,或者确定不存在这样的排列。
†^{\dagger}†长度为nnn的排列是一个数组,由nnn个不同的整数组成,这些整数从111到nnn按任意顺序排列。例如,[2,3,1,5,4][2,3,1,5,4][2,3,1,5,4]是一个排列,但[1,2,2][1,2,2][1,2,2]不是一个排列(222在数组中出现了两次),[1,3,4][1,3,4][1,3,4]也不是一个排列(n=3n=3n=3,但数组中有444)。
分析:
首先我们可以发现,排列ppp的哈曼顿值一定为偶数,不存在为奇数的情况。
因此无解的情况有两种:
- kkk为奇数或者
- 超过排列ppp的最大哈曼顿值
对于有解的情况,考虑构造。
首先,令我们要得到的排列ppp为单调递增的,也就是pi=i(1≤i≤n)p_i=i(1≤i≤n)pi=i(1≤i≤n),那么此时ppp的哈曼顿值为000。很明显,如果在此基础上交换两个数x,yx,yx,y,那么ppp的哈曼顿值便会增加2×∣x−y∣2×∣x−y∣2×∣x−y∣。也就是说,可以将一些数字两两配对,由于它们所产生的贡献值是互不干扰的,所以相当于是将k2\frac{k}{2}2k拆分成若干个不超过n−1n−1n−1,且互不相等的数的和。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 200010;
const LL MOD = 998244353;
LL a[N];
void solve() {
LL n, k;
cin >> n >> k;
if (k % 2 == 1)
cout << "No" << endl;
else {
LL ans = 0;
for (LL i = 1, j = n; i <= n; i++, j--) {
a[i] = i;
ans += abs(j - i);
}
if (k > ans)
cout << "No" << endl;
else {
LL x = k / 2;
for (LL i = 1, j = min(x, n - 1);; i++) {
swap(a[i], a[i + j]);
x -= j;
j = min(x, n - 2 * i - 1);
if (!x)
break;
}
cout << "Yes" << endl;
for (LL i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
}
}
}
int main() {
LL t;
cin >> t;
while (t--)
solve();
return 0;
}
D.Elections(贪心)
题意:
伯兰正在举行选举。有nnn名候选人参加选举,编号从111到nnn。第iii名候选人有aia_iai名粉丝会投票给他。此外,还有ccc人对自己喜欢的候选人举棋不定,我们姑且称他们为 “举棋不定者”。未决定的人会把票投给编号最小的候选人。
获得最高票数的候选人赢得选举,如果多个候选人获得相同的最高票数,则其中票数最低的候选人获胜。
您觉得这些选举太无聊且难以预测,因此决定将一些候选人排除在外。如果您不允许iii号候选人参加选举,那么他的所有aia_iai个粉丝都会变得犹豫不决,并将票投给编号最小的候选人。
你很想知道,从111到nnn的每个iii中,第iii号候选人要想赢得选举,至少需要排除多少个候选人。
分析:
直接把ccc加到a1a_1a1上,之后求出aaa中最靠前的最大值编号kkk,显然kkk的答案为000。
考虑其他xxx的答案。由于axa_xax本身不是最大值,即使是排除目前最大的aka_kak,这一部分也会加到编号最小的aia_iai上,最大值一定不降。所以至少要将xxx之前的x−1x−1x−1个人全部排除,才能使xxx为编号最小,增加axa_xax让其最大。
所以维护aaa的前缀和sss,若sx≥aks_x≥a_ksx≥ak,只需要把前面全去掉即可,答案为x−1x−1x−1。否则需要把前面全去掉,并且把xxx后面的最大值aka_kak也去掉,答案为xxx。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
const LL MOD = 998244353;
LL n, c, a[N], s[N];
void solve() {
cin >> n >> c;
for (int i = 1; i <= n; i++)
cin >> a[i];
a[1] += c;
LL maxx = 1;
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + a[i];
if (a[maxx] < a[i])
maxx = i;
}
for (int i = 1; i <= n; i++) {
if (i == maxx) {
cout << "0" << " ";
} else {
if (s[i] >= a[maxx]) {
cout << i - 1 << " ";
} else {
cout << i << " ";
}
}
}
cout << endl;
}
int main() {
LL t;
cin >> t;
while (t--)
solve();
return 0;
}
E.Computing Machine(贪心、数学)
题意:
萨沙有两个长度相同的二进制字符串sss和ttt,长度为nnn,由字符"0"和"1"组成。
还有一台计算器可以对长度相同的二进制字符串aaa和bbb进行两种运算kkk:
- 如果ai=ai+2=0a_{i}=a_{i+2}=0ai=ai+2=0,则可以赋值bi+1:=1b_{i+1}:=1bi+1:=1(1≤i≤k−21\le i\le k-21≤i≤k−2)。
- 如果bi=bi+2=1b_{i}=b_{i+2}=1bi=bi+2=1,则可以赋值ai+1:=1a_{i+1}:=1ai+1:=1(1≤i≤k−21\le i\le k-21≤i≤k−2)。
萨沙对下面的问题产生了兴趣:如果我们考虑字符串a=slsl+1…sra=s_ls_{l+1}\ldots s_ra=slsl+1…sr和字符串b=tltl+1…trb=t_lt_{l+1}\ldots t_rb=tltl+1…tr,那么使用计算器最多可以在字符串aaa中得到多少个"1"字符。由于萨沙非常好奇但很懒惰,所以由你来回答他感兴趣的几对字符串(li,ri)(l_i,r_i)(li,ri)的问题。
分析:
对于区间,寻找最优操作方案,我们贪心地考虑,先用sss的000增加ttt的111,再用ttt的111使sss的111变多,这样做一定是最优的,保证了第一步之后ttt中的111最多,从而最终sss中的111也最多。
接着我们考虑每一位会怎样被改变。若sis_isi被变为111,需要ti−1=1t_{i−1}=1ti−1=1,同时ti+1=1t_{i+1}=1ti+1=1和ttt的这两位又会受到si−2,si,si+2s_{i−2},s_i,s_{i+2}si−2,si,si+2的影响,所以对于每一位sis_isi,都只会受到[i−2,i+2][i−2,i+2][i−2,i+2]区间内的影响。
因此可以提前对整个区间进行操作,并对最终的sss求前缀和。对于询问长度不超过444的情况暴力操作求解。超过444时由于[l+2,r−2][l+2,r−2][l+2,r−2]只受区间[l,r][l,r][l,r]内的影响,答案不变,记录下原来的答案,再单独对[l,l+4][l,l+4][l,l+4]操作并记录前两位的答案,然后对[r−4,r][r−4,r][r−4,r]操作并记录后两位的答案,最后三部分相加即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2e5 + 10;
LL n, q, pre[N], s[N], t[N], t1[N], t2[N];
char ss[N], st[N];
void deal(int l, int r) {
int len = r - l + 1;
for (int i = l; i <= r; i++) {
t1[i - l + 1] = s[i];
t2[i - l + 1] = t[i];
}
for (int i = 1; i + 2 <= len; i++) {
if (!t1[i] && !t1[i + 2])
t2[i + 1] = 1;
}
for (int i = 1; i + 2 <= len; i++) {
if (t2[i] && t2[i + 2])
t1[i + 1] = 1;
}
for (int i = 1; i <= len; i++)
t1[i] += t1[i - 1];
}
void solve() {
cin >> n >> ss >> st >> q;
for (int i = 1; i <= n; i++) {
s[i] = ss[i - 1] - '0';
t[i] = st[i - 1] - '0';
}
deal(1, n);
for (int i = 1; i <= n; i++)
pre[i] = t1[i];
while (q--) {
LL l, r;
cin >> l >> r;
if (r - l < 4) {
deal(l, r);
cout << t1[r - l + 1] << endl;
} else {
int res = pre[r - 2] - pre[l + 1];
deal(l, l + 4);
res += t1[2];
deal(r - 4, r);
res += (t1[5] - t1[3]);
cout << res << endl;
}
}
}
int main() {
int T;
cin >> T;
while (T--)
solve();
return 0;
}
F.Large Graph(并查集)
题意:
给定一个长度为nnn的数组aaa。我们来构造一个大小为n×nn\times nn×n的方阵bbb,其中第iii行包含了循环右移(i−1)(i-1)(i−1)的数组aaa。例如,对于数组a=[3,4,5]a=[3,4,5]a=[3,4,5],得到的矩阵是
b=[345534453] b=\begin{bmatrix}3&4&5\\5&3&4\\4&5&3\end{bmatrix} b=354435543
让我们构建下面的图形:
- 该图包含n2n^2n2个顶点,每个顶点对应矩阵中的一个元素。我们把元素bi,jb_{i,j}bi,j对应的顶点记为(i,j)(i,j)(i,j)。
- 我们将在顶点(i1,j1)(i_1,j_1)(i1,j1)和(i2,j2)(i_2,j_2)(i2,j2)之间画一条边,如果∣i1−i2∣+∣j1−j2∣≤k|i_1-i_2|+|j_1-j_2|\le k∣i1−i2∣+∣j1−j2∣≤k和gcd(bi1,j1,bi2,j2)>1\gcd(b_{i_1,j_1},b_{i_2,j_2})\gt 1gcd(bi1,j1,bi2,j2)>1,其中gcd(x,y)\gcd(x,y)gcd(x,y)表示整数xxx和yyy的最大公约数)。
你的任务是计算所得图形中的连通块数†^{\dagger}†。
†^{\dagger}†图中的连通块是一个顶点集合,在这个集合中,任何顶点都可以通过边到达其他顶点,如果在这个集合中添加任何其他顶点,都会违反这一规则。
分析:
题目保证k≥2k≥2k≥2,说明斜着相邻的格子如果gcd>1gcd>1gcd>1,必定连边,很明显每个aia_iai在循环移位之后分成的至多两条全是aia_iai的斜线,所以除了数字为111的情况,对角线的斜线一定同属一个连通块。
相邻两条斜线的距离是111,由此我们可以把二维问题转为一维。使用logloglog分解质因数,预处理每个数的质因子,对于每个质因数找到上一次出现的位置,如果距离≤k≤k≤k就有边,并查集维护连通性,特判111的情况即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2e6 + 10;
int p[N], s[N], tot, k, a[N], lst[N], fa[N];
void init(int n) {
for (int i = 2; i <= n; ++i) {
if (!s[i])
s[i] = p[++tot] = i;
for (int j = 1; j <= tot && p[j] <= n / i; ++j) {
s[i * p[j]] = p[j];
if (!(i % p[j]))
break;
}
}
}
int n;
vector<int> used;
bool vis[N];
LL ans;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
if (find(x) != find(y))
fa[fa[x]] = fa[y];
}
void solve() {
cin >> n >> k;
for (int i = n; i < n + n; ++i) {
cin >> a[i];
a[i - n] = a[i];
}
int tmp = n;
n = (n << 1) - 1;
for (int i = 1; i <= n; ++i)
fa[i] = i;
for (int i = 1; i <= n; ++i) {
int x = a[i];
while (x != 1) {
int now = s[x];
int pre = lst[now];
if (pre && i - pre <= k)
merge(i, pre);
lst[now] = i;
while (!(x % now))
x /= now;
if (!vis[now]) {
vis[now] = true;
used.push_back(now);
}
}
}
for (int i = 1; i <= n; ++i) {
if (a[i] == 1)
ans += tmp - abs(tmp - i);
else if (find(i) == i)
++ans;
}
cout << ans << endl;
ans = 0;
for (int x: used) {
vis[x] = false;
lst[x] = 0;
}
used.clear();
}
int main() {
init(1000000);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

Codeforces Round 953 (Div. 2) A~F题解
1372

被折叠的 条评论
为什么被折叠?



