A.Maximize the Last Element(枚举)
题意:
给你一个由 n n n个整数组成的数组 a a a,其中 n n n是奇数。
在一次操作中,你将从数组 a a a中删除两个相邻的元素,然后将数组的剩余部分连接起来。例如,在数组 [ 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]。
我们将重复执行这一操作,直到 a a a中只剩下一个元素。
求 a a a中剩余元素的可能的最大值。
分析:
遍历数组,对于每个位置检查两侧数的数量是否为奇数,若为奇数则只有该位置可以保留。
代码:
#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 − 1 n-1 n−1个整数组成的数组 b b b。
如果 b i = a i & a i + 1 b_i=a_i\,\&\,a_{i+1} bi=ai&ai+1为 1 ≤ i ≤ n − 1 1\le i\le n-1 1≤i≤n−1,其中 & \& &表示按位与,那么由 n n n个整数组成的数组 a a a称为好数组。
构造一个好数组,或输出不存在好数组。
分析:
对于每一位分别考虑,若 b i b_i bi该位为 1 1 1,则 a i a_i ai和 a i + 1 a_{i+1} ai+1的这一位都必须是 1 1 1;默认其余的都为 0 0 0,最后检验是否合法即可。
代码:
#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(暴力)
题意:
给你一个由 n n n个整数组成的数组 a a a。
在一次操作中,将执行以下两步移动:
- 选择一个整数 x x x( 0 ≤ x ≤ 1 0 9 0\le x\le 10^{9} 0≤x≤109)。
- 将每个 a i a_i ai替换为 ∣ a i − x ∣ |a_i-x| ∣ai−x∣,其中 ∣ v ∣ |v| ∣v∣表示 v v v的绝对值。
例如,选择 x = 8 x=8 x=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]。
构建一个操作序列,使 a a a中的所有元素在最多 40 40 40次操作中等于 0 0 0,或者确定这是不可能的。不需要尽量减少运算次数。
分析:
最多操作 40 40 40次,考虑暴力。
设当前最大值为 m a x x maxx maxx,最小值为 m i n n minn minn。每次减去 ( m a x x − m i n n ) / 2 + m i n n (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(思维)
题意:
给你一个无向图,图中有 n n n个顶点,编号从 1 1 1到 n n n。当且仅当 u ⊕ v u\oplus v u⊕v是质数时,顶点 u u u和 v v v之间有一条边,其中 ⊕ \oplus ⊕表示按位异或。
用最少的颜色给图中的所有顶点着色,使得由一条边直接连接的两个顶点没有相同的颜色。
分析:
我们注意到如果两个整数之差为 4 4 4的倍数,那么他们异或也一定是 4 4 4的倍数,显然是一个合数。
由此可以推出四种颜色的万能方案。
观察样例发现 n = 6 n=6 n=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(构造)
题意:
这是一个互动问题。
考虑一个由 n n n个顶点和 m m m条边组成的无向连接图。每个顶点可以用三种颜色中的一种着色: 1 1 1、 2 2 2或 3 3 3。初始时,所有顶点都未着色。
爱丽丝和鲍勃正在玩一个包含 n n n轮的游戏。在每一轮中,都会发生以下两个步骤:
- 爱丽丝选择两种不同的颜色。
- 鲍勃选择一个未着色的顶点,并用爱丽丝选择的两种颜色之一为其着色。
如果存在连接两个相同颜色顶点的边,则爱丽丝获胜。否则,鲍勃获胜。
给你这个图。你的任务是决定你想扮演哪位玩家并赢得游戏。
分析:
读题猜测与二分图染色有关。原图要么是二分图要么有奇环,可以用染色法判断。
那么对于上述情况分类讨论:
-
原图存在奇环。那么肯定选爱丽丝,一直输出
12
即可,由于有奇环,所以鲍勃必输。 -
原图为二分图。首先选鲍勃。然后按照二分图把点分成两个集合, 1 1 1染左边, 2 2 2染右边,一个集合染没了另一个集合染另外 2 2 2种颜色即可,由于只有 3 3 3种颜色,所以一定会有 1 1 1个能染的颜色。
代码:
#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(思维)
题意:
给你 n n n根木棒,编号从 1 1 1到 n n n。第 i i i根木棒的长度是 a i a_i ai。
你需要回答 q q q个问题。在每个查询中,你都会得到两个整数 l l l和 r r r( 1 ≤ l < r ≤ n 1\le l\lt r\le n 1≤l<r≤n, r − l + 1 ≥ 6 r-l+1\ge 6 r−l+1≥6)。请判断是否可能从编号为 l l l至 r r r的小棒中选择 6 6 6根不同的小棒组成 2 2 2个非退化三角形 ∗ ^{\text{∗}} ∗。
∗ ^{\text{∗}} ∗如果边长为 a a a, b b b和 c c c的三角形叫做非退化三角形,那么:
- a < b + c a\lt b+c a<b+c
- b < a + c b\lt a+c b<a+c
- c < a + b c\lt a+b c<a+b
分析:
可以发现,先将区间排序一遍不会改变答案,以下默认查询的区间是不增的。
考虑存在解的充要条件是存在两个不相交的三元组 ( x , y , z ) (x,y,z) (x,y,z) ( x < y < z ) (x\lt y\lt z) (x<y<z)使得 a x < a y + a z a_x\lt a_y+a_z ax<ay+az。
注意到若每次取出最大的三个数 x , y , z x,y,z x,y,z,当 x ≥ y + z x≥y+z x≥y+z时,即不能构成三元组时,则有 x ≥ 2 z x≥2z x≥2z,此时最大值减半。又当最大值为 1 1 1时若数有至少 6 6 6个,显然必能构成至少两个三元组。考虑若无法找到满足条件的三元组,最大值会在 l o g n logn logn次后递减至 1 1 1。所以当查询的区间长度大于 k k k时,必然有解。其中 k k k是 O ( l o g n ) O(logn) O(logn)的。
考虑当 l e n < k len\lt k len<k时如何求解。设当前第 i i i大为 c i c_i ci,注意到当 c 1 ≥ c 2 + c 3 c_1≥c_2+c_3 c1≥c2+c3时 c 1 c_1 c1是无用的,直接删去即可。
否则考虑前 6 6 6大值,枚举每种情况看能否组成两个三元组。若不能,则让 c 1 , c 2 , c 3 c_1,c_2,c_3 c1,c2,c3直接组成一个三元组并删去即可。
该贪心正确的证明如下:若两个三元组的最大值均在前 6 6 6大中,则将后面的数替换成前 6 6 6大是不劣的。否则利用前 3 3 3大构成一个三元组也是不劣的。
代码:
#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(构造)
题意:
给你一个由 n n n行和 m m m列组成的网格,其中每个单元格最初都是白色的。此外,你还会得到一个整数 k k k,其中 1 ≤ k ≤ min ( n , m ) 1\le k\le\min(n,m) 1≤k≤min(n,m)。
你将处理两种类型的 q q q操作:
- H \mathtt{H} H(水平操作)-在网格中选择一个完全位于网格内的 1 × k 1\times k 1×k矩形,该矩形中的所有单元格都是白色的。然后,该矩形中的所有单元格都变为黑色。
- V \mathtt{V} V(垂直操作)-在网格中选择一个 k × 1 k\times 1 k×1矩形,该矩形中的所有单元格都是白色。然后,该矩形中的所有单元格都将变为黑色。
每次操作后,如果有任何行或列变为全黑,这些行或列中的所有单元格都会同时重置为白色。具体来说,如果某个单元格所在行和列的所有单元格都变为黑色,则该行和该列中的所有单元格都将重置为白色。
选择矩形时,应确保可以执行所有给定的操作,或者确定这是不可能的。
分析:
以下是确保可以无限执行操作的策略:
- 只对最左边的 k k k列进行水平操作,只对最上面的 k k k行进行垂直操作。
- 优先执行会导致重置的操作。如果多个操作会导致重置,则执行其中任何一个操作;如果没有操作会导致重置,则执行任何可用操作。
对于一个 n × m n×m n×m网格,如果它的黑色单元格可以被不重叠的 1 × m 1×m 1×m矩形无间隙地覆盖,我们就定义它处于水平状态。同样,如果网格中的黑色单元格可以被不重叠的 n × 1 n×1 n×1矩形无间隙地覆盖,则网格处于垂直状态。
我们的策略可以确保网格始终满足以下属性:
- 左上角的 k × k k×k k×k区域、左下角的 ( n − k ) × k (n−k)×k (n−k)×k区域和右上角的 k × ( m − k ) k×(m−k) k×(m−k)区域要么处于水平状态,要么处于垂直状态,要么两者都处于水平状态。
- 左上角和左下角区域不能都纯粹处于垂直状态;同样,左上角和右上角区域也不能都纯粹处于水平状态。
可以证明当满足这些属性时,总有一个有效的操作;根据我们的策略执行操作后,网格仍然满足这些属性。
有效操作证明:
假设当前操作是水平操作(垂直操作的证明类似)。如果左上角和左下角区域无法执行水平操作,那么这两个区域必须是全黑或纯垂直状态。如果其中一个区域是全黑的,那么另一个区域就不可能是全黑或纯垂直状态,因为那样会导致重置。根据第二个属性,左上角和左下角区域不能都处于纯垂直状态。因此,总有一个操作是有效的。
保持第一个属性证明:
对于左下角和右上角区域,它们始终满足第一属性。证明考虑左下角区域(右上角区域的证明类似)。它开始时是全白的,然后逐行变黑,保持水平状态。一旦完全变黑,它就会逐列重置,保持垂直状态。因此,它在水平和垂直状态之间交替。
对于左上角区域,它始终满足第一个属性。证明在着色和重置之前,左上角区域仍然满足第一个属性。假设它在重置前处于水平状态(垂直状态的证明类似)。由于它不能垂直重置,因此重置后至少仍处于水平状态。如果它完全变黑并水平重置(垂直重置的证明类似),它仍处于水平状态。除非 k = 1 k=1 k=1,否则行与列不能同时复位。假设行和列都可以重置,并假设当前操作在左上角区域是水平的(垂直操作的证明类似)。这意味着左上角区域在完全变黑之前处于纯水平状态。右上角区域的一行是完全黑色的,而其他行是白色的,这与操作前的第二个属性相矛盾。
保持第二属性证明:
假设当前操作是水平操作(垂直操作的证明类似)并导致重置,则第二属性仍然成立。证明如果操作位于左下角区域,则重置前该区域为全黑。重置后,左上角区域变为全白,保持第二个性质。我们之前证明过,除非 k = 1 k=1 k=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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。