Codeforces Round #648 (Div. 2)
https://codeforces.com/contest/1365
A - Matrix Game
取空行和空列的最小值,判断其奇偶性。
B - Trouble Sort
仔细读题,任意两个不同type的元素就可以交换位置,而不需要相邻。所以并不是要保持各个type里内部有序,只需要存在两种type就可以实现交换。
C - Rotation Matching
好像做过了。
题意:给一个 \(n\) 个数的数组 \(a\) ,和一个 \(n\) 个数的数组 \(b\) ,对 \(b\) 进行若干次循环右移,使得对应位置和 \(a\) 中元素相等的元素个数最多,求最多有多少种元素。
题解:对于 \(b\) 中的每个元素,计算其与 \(a\) 中相等的元素对应时需要循环右移的次数,弄个cnt记录一下这个次数。
int n;
int a[200005];
vector<int> idx[200005];
int cnt[200005];
void TestCase() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
idx[i].clear();
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
idx[a[i]].push_back(i);
}
for(int i = 1; i <= n; ++i)
cnt[i] = 0;
for(int i = 1, b; i <= n; ++i) {
scanf("%d", &b);
for(auto &v : idx[b]) {
int pos = i - v;
if(pos <= 0)
pos += n;
++cnt[pos];
}
}
printf("%d\n", *max_element(cnt + 1, cnt + 1 + n));
return;
}
D - Solve The Maze
题意:给一个矩形迷宫,有空地、墙、好人、坏人。问是否能在空地够加入一些墙,使得所有的好人都能移动到迷宫右下角而所有的坏人都不能移动到迷宫右下角。
题解:贪心,把坏人旁边的空地都变成墙,这样所有的坏人都不能移动且占用的空间最小,此时若有个好人挡住了墙(也就是这俩人相邻),就是No。然后从迷宫右下角开始找连通块,最后看看是不是可以从右下角走到所有的好人处。
注意特殊判断一些情况。
E - Maximum Subsequence Value
题意:给一个 \(n(n\leq 500)\) 的数组,从中选出一个子集,假设其大小为 \(k\) 。这个子集的价值,来源于各个二进制位的贡献,具体来说,若第 \(i\) 个二进制位 \(2^i\) 在这个子集中被set了至少 \(max(1, k-2)\) 次,则会产生贡献 \(2^i\) 。求最大的价值。
题解:一开始以为要用什么Trie去从最高位贪心,但是发现不太对,因为有些元素不知道选不选。又想了想会不会是枚举缺少最高位的两个元素,但是这样并不见得最优。后来突然想到,要使得第 \(i\) 个二进制位 \(2^i\) 在这个子集中被set了至少 \(max(1, k-2)\) 次,则需要任意3个元素的OR和都是set状态,仔细想想若是每个集合只有3个元素,就恰好是这3个元素的OR和,再加入新的元素并不可能会引入新的位,因为新的位必定在原本的3个元素中都没有出现过,再看一下 \(n\leq 500\) ,基本就是这样写了。
在实现上面,直接来三重循环就可以了,若是某个下标重合,则正好枚举了只选1个或者只选2个的情况。
int n;
ll a[505];
void TestCase() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
ll ans = 0;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
ll tmp = a[i] | a[j];
for(int k = 1; k <= n; ++k)
ans = max(ans, tmp | a[k]);
}
}
printf("%lld\n", ans);
return;
}
*F - Swaps Again
题意:给一个 \(n\) 个数的数组 \(a\) 和 \(n\) 个数的数组 \(b\) ,定义一种操作:选择一个长度 \(k\leq\lfloor \frac{n}{2}\rfloor\) ,然后交换长度为 \(k\) 的前缀和长度为 \(k\) 的后缀,例如:若 \(k=2\) :
1 2 3 4 5 6
5 6 3 4 1 2
问数组 \(a\) 是否能变成数组 \(b\) 。
题解:首先若这个数组是奇数长度则中心的这个位置肯定是没办法移动的,可以直接判断并去除,得到了一个偶数长度的数组,当把这个数组蛇形排列得到:
1 2 3 4
8 7 6 5
就可以发现一次操作实际上是选择包含左侧的一组数,然后旋转180度。
虽然这个观察没啥用就是了。
通过题目给的操作,可以组合一系列操作来完成翻转某个上下位置。比如翻转第3组:
1 2 3 4
8 7 6 5
6 7 8 4
3 2 1 5
3 7 8 4
6 2 1 5
1 2 6 4
8 7 3 5
所以可以用3次操作来翻转一个对称位置。
从上面的分析可以知道,对称位置是无序的。
假如有奇奇怪怪的直觉,就能感受到,不存在其他的变换。观察上面的步骤发现,上下位置可以翻转,且可以随意排列他们。
int n;
int a[505];
int b[505];
pii pa[505];
pii pb[505];
void TestCase() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &b[i]);
if(n % 2 == 1 && a[(n + 1) / 2] != b[(n + 1) / 2]) {
puts("No");
return;
}
for(int i = 1; i <= n / 2; ++i) {
if(a[i] > a[n - i + 1])
swap(a[i], a[n - i + 1]);
pa[i] = {a[i], a[n - i + 1]};
}
for(int i = 1; i <= n / 2; ++i) {
if(b[i] > b[n - i + 1])
swap(b[i], b[n - i + 1]);
pb[i] = {b[i], b[n - i + 1]};
}
sort(pa + 1, pa + 1 + n / 2);
sort(pb + 1, pb + 1 + n / 2);
for(int i = 1; i <= n / 2; ++i) {
if(pa[i] != pb[i]) {
puts("No");
return;
}
}
puts("Yes");
return;
}
*G - Secure Password
题意:题目内置一个 \(n\leq 1000\) 个数的数组,每个数字有长达63位,你可以询问至多13次,每次询问可以询问任意个位置,评测机返回你问的这些位置的OR值,要求复原出这个数组对应的密码数组。具体来说,密码数组的第 \(i\) 个数就是原数组除了第 \(i\) 个数以外的所有数的OR。
题解:这个和前面那个二分找最大值的不一样,这个至多13次的来源不是二分。尝试从质数、二进制编码的角度入手都是不对的。正解是这样:需要选择一套编码,这套编码里面任意两个码直接不存在完全包含的关系,易知这些码必须有有相同数量的1才会拥有最多的码。 \(C_13^6=C_13^7\) 是超过 \(1000\) 的最小的值,这个13就是题目中13的来源。
所以先生成1000个这样的码,然后给所有的元素编码,第 \(b\) 次询问就问所有第 \(b\) 位为1的值的OR。要求“原数组除了第 \(i\) 个数以外的所有数的OR”,就把这个第 \(i\) 个数的编码中为0的值对应的询问全部或起来,容易明白这样就包含了其他所有的 \(n-1\) 个数。
int n;
int idx[1005];
ll res[13];
void TestCase() {
scanf("%d", &n);
int top = 0;
for(int x = 0; x < (1 << 13); ++x) {
if(__builtin_popcount(x) != 6)
continue;
idx[++top] = x;
if(top == n)
break;
}
for(int b = 0; b < 13; ++b) {
int cnt = 0;
for(int i = 1; i <= n; ++i) {
if(idx[i] & (1 << b))
++cnt;
}
if(cnt == 0)
continue;
printf("? %d", cnt);
for(int i = 1; i <= n; ++i) {
if(idx[i] & (1 << b))
printf(" %d", i);
}
printf("\n");
fflush(stdout);
scanf("%lld", &res[b]);
}
printf("!");
for(int i = 1; i <= n; ++i) {
ll Ai = 0;
for(int b = 0; b < 13; ++b) {
if(!(idx[i] & (1 << b)))
Ai |= res[b];
}
printf(" %lld", Ai);
}
printf("\n");
fflush(stdout);
return;
}
注:这个定理叫做 Sperner's theorem 。 Sperner's theorem, in discrete mathematics, describes the largest possible families of finite sets none of which contain any other sets in the family.