A.Maximize the Last Element(枚举)
题意:
给你一个由nnn个整数组成的数组aaa,其中nnn是奇数。
在一次操作中,你将从数组aaa中删除两个相邻的元素,然后将数组的剩余部分连接起来。例如,在数组[4,7,4,2,9][4,7,4,2,9][4,7,4,2,9]中,我们可以通过操作[4,7‾,4,2,9]→[4,2,9][\underline{4,7},4,2,9]\to[4,2,9][4,7,4,2,9]→[4,2,9]和[4,7,4,2‾,9]→[4,7,9][4,7,\underline{4,2},9]\to[4,7,9][4,7,4,2,9]→[4,7,9]分别得到数组[4,2,9][4,2,9][4,2,9]和[4,7,9][4,7,9][4,7,9]。然而,我们无法得到数组[7,2,9][7,2,9][7,2,9],因为需要删除不相邻的元素[4‾,7,4‾,2,9][\underline{4},7,\underline{4},2,9][4,7,4,2,9]。
我们将重复执行这一操作,直到aaa中只剩下一个元素。
求aaa中剩余元素的可能的最大值。
分析:
遍历数组,对于每个位置检查两侧数的数量是否为奇数,若为奇数则只有该位置可以保留。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int a[N];
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; ++i) {
if ((i - 1) % 2 == 1 || (n - i) % 2 == 1)
continue;
ans = max(ans, a[i]);
}
cout << ans << endl;
}
return 0;
}
B.AND Reconstruction(枚举)
题意:
给你一个由n−1n-1n−1个整数组成的数组bbb。
如果bi=ai & ai+1b_i=a_i\,\&\,a_{i+1}bi=ai&ai+1为1≤i≤n−11\le i\le n-11≤i≤n−1,其中&\&&表示按位与,那么由nnn个整数组成的数组aaa称为好数组。
构造一个好数组,或输出不存在好数组。
分析:
对于每一位分别考虑,若bib_ibi该位为111,则aia_iai和ai+1a_{i+1}ai+1的这一位都必须是111;默认其余的都为000,最后检验是否合法即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, a[N], b[N], c[N];
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i < n; ++i)
cin >> b[i];
for (int i = 1; i <= n; ++i) {
a[i] = 0;
}
int flag = 1;
for (int j = 0; j < 30 && flag; ++j) {
for (int i = 1; i <= n; ++i)
c[i] = 0;
for (int i = 1; i < n; ++i)
if ((b[i] >> j) & 1)
c[i] = c[i + 1] = 1;
for (int i = 1; i < n; ++i) {
if (!((b[i] >> j) & 1) && c[i] && c[i + 1]) {
flag = 0;
break;
}
}
for (int i = 1; i <= n; ++i) {
if (c[i])
a[i] |= (1 << j);
}
}
if (!flag) {
cout << "-1" << endl;
continue;
}
for (int i = 1; i <= n; ++i)
cout << a[i] << " ";
cout << endl;
}
return 0;
}
C.Absolute Zero(暴力)
题意:
给你一个由nnn个整数组成的数组aaa。
在一次操作中,将执行以下两步移动:
- 选择一个整数xxx(0≤x≤1090\le x\le 10^{9}0≤x≤109)。
- 将每个aia_iai替换为∣ai−x∣|a_i-x|∣ai−x∣,其中∣v∣|v|∣v∣表示vvv的绝对值。
例如,选择x=8x=8x=8后,数组[5,7,10][5,7,10][5,7,10]将变为[∣5−8∣,∣7−8∣,∣10−8∣]=[3,1,2][|5-8|,|7-8|,|10-8|]=[3,1,2][∣5−8∣,∣7−8∣,∣10−8∣]=[3,1,2]。
构建一个操作序列,使aaa中的所有元素在最多404040次操作中等于000,或者确定这是不可能的。不需要尽量减少运算次数。
分析:
最多操作404040次,考虑暴力。
设当前最大值为maxxmaxxmaxx,最小值为minnminnminn。每次减去(maxx−minn)/2+minn(maxx-minn)/2+minn(maxx−minn)/2+minn,这样操作之后,整体区间上界必然减半。可以计算出如果有解,操作次数一定足够。
代码:
#include<bits/stdc++.h>
using namespace std;
int a[200005];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int q = 40;
vector<int> ans;
while (q--) {
int maxx = -1;
int minn = 1e9 + 1;
for (int i = 1; i <= n; i++) {
maxx = max(maxx, a[i]);
minn = min(minn, a[i]);
}
int tmp = max(1, (maxx - minn) / 2 + minn);
if (maxx - minn == 0) {
ans.push_back(maxx);
for (int i = 1; i <= n; i++) {
a[i] = 0;
}
break;
}
for (int i = 1; i <= n; i++) {
a[i] = abs(a[i] - tmp);
}
ans.push_back(tmp);
}
bool flag = true;
for (int i = 1; i <= n; i++) {
if (a[i]) {
flag = false;
break;
}
}
if (flag) {
cout << ans.size() << endl;
for (auto x: ans) {
cout << x << ' ';
}
cout << endl;
} else {
cout << "-1" << endl;
}
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D.Prime XOR Coloring(思维)
题意:
给你一个无向图,图中有nnn个顶点,编号从111到nnn。当且仅当u⊕vu\oplus vu⊕v是质数时,顶点uuu和vvv之间有一条边,其中⊕\oplus⊕表示按位异或。
用最少的颜色给图中的所有顶点着色,使得由一条边直接连接的两个顶点没有相同的颜色。
分析:
我们注意到如果两个整数之差为444的倍数,那么他们异或也一定是444的倍数,显然是一个合数。
由此可以推出四种颜色的万能方案。
观察样例发现n=6n=6n=6时需要四种颜色,对更小的数据参考样例进行特判即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
LL n;
cin >> n;
if (n <= 5) {
cout << n / 2 + 1 << endl;
for (int i = 1; i <= n; ++i)
cout << i / 2 + 1 << " ";
cout << endl;
} else {
cout << 4 << endl;
for (int i = 1; i <= n; ++i)
cout << (i % 4) + 1 << " ";
cout << endl;
}
}
return 0;
}
E.Coloring Game(构造)
题意:
这是一个互动问题。
考虑一个由nnn个顶点和mmm条边组成的无向连接图。每个顶点可以用三种颜色中的一种着色:111、222或333。初始时,所有顶点都未着色。
爱丽丝和鲍勃正在玩一个包含nnn轮的游戏。在每一轮中,都会发生以下两个步骤:
- 爱丽丝选择两种不同的颜色。
- 鲍勃选择一个未着色的顶点,并用爱丽丝选择的两种颜色之一为其着色。
如果存在连接两个相同颜色顶点的边,则爱丽丝获胜。否则,鲍勃获胜。
给你这个图。你的任务是决定你想扮演哪位玩家并赢得游戏。
分析:
读题猜测与二分图染色有关。原图要么是二分图要么有奇环,可以用染色法判断。
那么对于上述情况分类讨论:
-
原图存在奇环。那么肯定选爱丽丝,一直输出
12即可,由于有奇环,所以鲍勃必输。 -
原图为二分图。首先选鲍勃。然后按照二分图把点分成两个集合,111染左边,222染右边,一个集合染没了另一个集合染另外222种颜色即可,由于只有333种颜色,所以一定会有111个能染的颜色。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
vector<int> e[N];
int col[N], flag, l[N], r[N];
void dfs(int u, int c) {
col[u] = c;
for (int v: e[u]) {
if (!col[v])
dfs(v, 3 - c);
else if (col[v] != 3 - c) {
flag = 0;
return;
}
}
}
int n, m, u, v, cntl, cntr;
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
col[i] = 0;
e[i].clear();
}
for (int i = 1; i <= m; i++) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
flag = 1;
dfs(1, 1);
if (flag == 0) {
cout << "Alice" << endl;
cout.flush();
for (int i = 1; i <= n; i++) {
cout << "1 2" << endl;
cout.flush();
cin >> u >> v;
}
} else {
cout << "Bob" << endl;
cout.flush();
cntl = cntr = 0;
for (int i = 1; i <= n; i++) {
if (col[i] == 1)
l[++cntl] = i;
else
r[++cntr] = i;
}
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
if (a > b)
swap(a, b);
if (a == 1) {
if (cntl)
cout << l[cntl--] << " " << a << endl;
else
cout << r[cntr--] << " " << b << endl;
} else {
if (cntr)
cout << r[cntr--] << " " << b << endl;
else
cout << l[cntl--] << " " << a << endl;
}
cout.flush();
}
}
}
return 0;
}
F.Triangle Formation(思维)
题意:
给你nnn根木棒,编号从111到nnn。第iii根木棒的长度是aia_iai。
你需要回答qqq个问题。在每个查询中,你都会得到两个整数lll和rrr(1≤l<r≤n1\le l\lt r\le n1≤l<r≤n,r−l+1≥6r-l+1\ge 6r−l+1≥6)。请判断是否可能从编号为lll至rrr的小棒中选择666根不同的小棒组成222个非退化三角形∗^{\text{∗}}∗。
∗^{\text{∗}}∗如果边长为aaa,bbb和ccc的三角形叫做非退化三角形,那么:
- a<b+ca\lt b+ca<b+c
- b<a+cb\lt a+cb<a+c
- c<a+bc\lt a+bc<a+b
分析:
可以发现,先将区间排序一遍不会改变答案,以下默认查询的区间是不增的。
考虑存在解的充要条件是存在两个不相交的三元组(x,y,z)(x,y,z)(x,y,z)(x<y<z)(x\lt y\lt z)(x<y<z)使得ax<ay+aza_x\lt a_y+a_zax<ay+az。
注意到若每次取出最大的三个数x,y,zx,y,zx,y,z,当x≥y+zx≥y+zx≥y+z时,即不能构成三元组时,则有x≥2zx≥2zx≥2z,此时最大值减半。又当最大值为111时若数有至少666个,显然必能构成至少两个三元组。考虑若无法找到满足条件的三元组,最大值会在lognlognlogn次后递减至111。所以当查询的区间长度大于kkk时,必然有解。其中kkk是O(logn)O(logn)O(logn)的。
考虑当len<klen\lt klen<k时如何求解。设当前第iii大为cic_ici,注意到当c1≥c2+c3c_1≥c_2+c_3c1≥c2+c3时c1c_1c1是无用的,直接删去即可。
否则考虑前666大值,枚举每种情况看能否组成两个三元组。若不能,则让c1,c2,c3c_1,c_2,c_3c1,c2,c3直接组成一个三元组并删去即可。
该贪心正确的证明如下:若两个三元组的最大值均在前666大中,则将后面的数替换成前666大是不劣的。否则利用前333大构成一个三元组也是不劣的。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, a[N], p, l, r, c[15];
priority_queue<int> s;
bool check(int x, int y, int z) {
int xx = 0, yy = 0, zz = 0;
for (int i = 2; i <= 6; i++)
if (i != x && i != y && i != z) {
if (!xx)
xx = i;
else if (!yy)
yy = i;
else
zz = i;
}
return (c[x] < c[y] + c[z] && c[xx] < c[yy] + c[zz]);
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
while (m--) {
while (!s.empty())
s.pop();
cin >> l >> r;
if (r - l + 1 > 80) {
cout << "YES" << endl;
continue;
}
for (int i = l; i <= r; i++)
s.emplace(a[i]);
p = 2;
while (p && (int) s.size() >= p * 3) {
if (p == 1) {
for (int i = 1; i <= 3; i++)
c[i] = s.top(), s.pop();
if (c[1] < c[2] + c[3]) {
p = 0;
break;
}
s.emplace(c[2]), s.emplace(c[3]);
continue;
}
for (int i = 1; i <= 6; i++)
c[i] = s.top(), s.pop();
for (int i = 2; i <= 6; i++)
for (int j = i + 1; j <= 6; j++)
if (check(1, i, j)) {
p = 0;
break;
}
if (!p)
break;
else if (c[1] < c[2] + c[3]) {
p--;
for (int i = 4; i <= 6; i++)
s.emplace(c[i]);
} else
for (int i = 2; i <= 6; i++)
s.emplace(c[i]);
}
cout << (p ? "NO" : "YES");
cout << endl;
}
return 0;
}
G.Grid Reset(构造)
题意:
给你一个由nnn行和mmm列组成的网格,其中每个单元格最初都是白色的。此外,你还会得到一个整数kkk,其中1≤k≤min(n,m)1\le k\le\min(n,m)1≤k≤min(n,m)。
你将处理两种类型的qqq操作:
- H\mathtt{H}H(水平操作)-在网格中选择一个完全位于网格内的1×k1\times k1×k矩形,该矩形中的所有单元格都是白色的。然后,该矩形中的所有单元格都变为黑色。
- V\mathtt{V}V(垂直操作)-在网格中选择一个k×1k\times 1k×1矩形,该矩形中的所有单元格都是白色。然后,该矩形中的所有单元格都将变为黑色。
每次操作后,如果有任何行或列变为全黑,这些行或列中的所有单元格都会同时重置为白色。具体来说,如果某个单元格所在行和列的所有单元格都变为黑色,则该行和该列中的所有单元格都将重置为白色。
选择矩形时,应确保可以执行所有给定的操作,或者确定这是不可能的。
分析:
以下是确保可以无限执行操作的策略:
- 只对最左边的kkk列进行水平操作,只对最上面的kkk行进行垂直操作。
- 优先执行会导致重置的操作。如果多个操作会导致重置,则执行其中任何一个操作;如果没有操作会导致重置,则执行任何可用操作。
对于一个n×mn×mn×m网格,如果它的黑色单元格可以被不重叠的1×m1×m1×m矩形无间隙地覆盖,我们就定义它处于水平状态。同样,如果网格中的黑色单元格可以被不重叠的n×1n×1n×1矩形无间隙地覆盖,则网格处于垂直状态。
我们的策略可以确保网格始终满足以下属性:
- 左上角的k×kk×kk×k区域、左下角的(n−k)×k(n−k)×k(n−k)×k区域和右上角的k×(m−k)k×(m−k)k×(m−k)区域要么处于水平状态,要么处于垂直状态,要么两者都处于水平状态。
- 左上角和左下角区域不能都纯粹处于垂直状态;同样,左上角和右上角区域也不能都纯粹处于水平状态。
可以证明当满足这些属性时,总有一个有效的操作;根据我们的策略执行操作后,网格仍然满足这些属性。
有效操作证明:
假设当前操作是水平操作(垂直操作的证明类似)。如果左上角和左下角区域无法执行水平操作,那么这两个区域必须是全黑或纯垂直状态。如果其中一个区域是全黑的,那么另一个区域就不可能是全黑或纯垂直状态,因为那样会导致重置。根据第二个属性,左上角和左下角区域不能都处于纯垂直状态。因此,总有一个操作是有效的。
保持第一个属性证明:
对于左下角和右上角区域,它们始终满足第一属性。证明考虑左下角区域(右上角区域的证明类似)。它开始时是全白的,然后逐行变黑,保持水平状态。一旦完全变黑,它就会逐列重置,保持垂直状态。因此,它在水平和垂直状态之间交替。
对于左上角区域,它始终满足第一个属性。证明在着色和重置之前,左上角区域仍然满足第一个属性。假设它在重置前处于水平状态(垂直状态的证明类似)。由于它不能垂直重置,因此重置后至少仍处于水平状态。如果它完全变黑并水平重置(垂直重置的证明类似),它仍处于水平状态。除非k=1k=1k=1,否则行与列不能同时复位。假设行和列都可以重置,并假设当前操作在左上角区域是水平的(垂直操作的证明类似)。这意味着左上角区域在完全变黑之前处于纯水平状态。右上角区域的一行是完全黑色的,而其他行是白色的,这与操作前的第二个属性相矛盾。
保持第二属性证明:
假设当前操作是水平操作(垂直操作的证明类似)并导致重置,则第二属性仍然成立。证明如果操作位于左下角区域,则重置前该区域为全黑。重置后,左上角区域变为全白,保持第二个性质。我们之前证明过,除非k=1k=1k=1,否则行和列不能同时重置。如果水平操作在左上角区域,且列重置,则左上角区域在重置前是完全黑色的。重置后,左上角区域变为垂直,而左下角区域变为白色,保持第二个属性。如果在左上角区域进行了水平操作,且行重置,则左上角区域保持不变,而右上角区域的行变为白色。如果右上角区域的状态发生变化并重置为纯水平状态(唯一可能违反第二属性的情况),则右上角区域在重置前是完全黑色的。因此,左上角区域在重置前并不处于纯水平状态。由于左上角区域保持不变,因此它不可能处于纯水平状态,从而保持了第二个属性。
假设当前操作是水平操作(垂直操作的证明与此类似),并且不会导致重置,则第二个性质仍然成立。证明如果操作在左下角区域,则保持水平状态,维持第二个属性。如果操作在左上角区域,唯一可能违反第二属性的情况是右上角区域保持纯水平状态,而左上角区域变成纯水平状态。这意味着左上角区域在操作前是完全白色的。在这种情况下,我们可以选择重置右上角区域中任何完全黑色的行。根据策略,我们会优先重置,从而导致矛盾。因此,第二个性质仍然成立。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX_SIZE = 105;
char operationType;
int n, m, k, q, grid[MAX_SIZE][MAX_SIZE];
string s;
int calculateSum(int x1, int y1, int x2, int y2) {
int sum = 0;
for (int i = x1; i <= x2; i++)
for (int j = y1; j <= y2; j++)
sum += grid[i][j];
return sum;
}
void performOperation(int x, int y) {
cout << x << ' ' << y << '\n';
for (int i = 1; i <= k; i++) {
grid[x][y] = 1;
if (operationType == 'H')
y++;
else
x++;
}
int rowSums[MAX_SIZE] = {}, colSums[MAX_SIZE] = {};
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
rowSums[i] += grid[i][j];
colSums[j] += grid[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (rowSums[i] == m || colSums[j] == n)
grid[i][j] = 0;
}
void solve() {
cin >> n >> m >> k >> q >> s;
s = ' ' + s;
memset(grid, 0, sizeof(grid));
for (int i = 1; i <= q; i++) {
operationType = s[i];
if (operationType == 'H') {
int row = -1;
for (int j = 1; j <= n; j++)
if (calculateSum(j, 1, j, k) == 0) {
row = j;
if (calculateSum(j, 1, j, m) == m - k) {
break;
}
}
performOperation(row, 1);
} else {
int col = -1;
for (int j = 1; j <= m; j++)
if (calculateSum(1, j, k, j) == 0) {
col = j;
if (calculateSum(1, j, n, j) == n - k) {
break;
}
}
performOperation(1, col);
}
}
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

1165

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



