CF Round 770 Div2 题解
A题 Reverse and Concatenate (思维)
我们记 \(rev(s)\) 为 \(s\) 的反转串。
给定一个长度为 \(n\) 的字符串,我们可以对其进行操作,用 \(s+rev(s)\) 或者 \(rev(s)+s\) 代替原字符串 \(s\)。
问,在经历恰好 \(k\) 次操作后,我们可能得到多少不同的字符串(不统计中途产生的那些)?
\(n\leq 100,k\leq 1000\)
当 \(k=0\) 或者原字符串就是回文串的时候,答案显然为 1。
当 \(s\) 并非回文串且 \(k>0\) 的时候,我们经历一次操作后得到了 \(s+rev(s)\) 和 \(rev(s)+s\) 两个字符串。不难发现,这两个字符串都是回文串,而一个回文串不论进行多少次操作,其种类都不会变多,仅为 1。考虑到 \(s\) 并非回文串,所以 \(s+rev(s)\) 和 \(rev(s)+s\) 并非同一个字符串,答案为 2。
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, k;
char s[N];
int solve() {
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
if (k == 0) return 1;
bool flag = true;
for (int i = 1; i <= n; ++i)
if (s[i] != s[n + 1 - i]) flag = false;
return flag ? 1 : 2;
}
int main()
{
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
B题 Fortune Telling (构造)
给定一个长度为 \(n\) 的数列 \(\{a_n\}\) 和两个整数 \(x\), \(y\)。
我们可以以这个数组为基础,玩一个游戏:我们选择一个数 \(d\) 作为起点,然后从 1 到 n 分别进行操作,每次可以选择将 \(d\) 加上\(a_i\) 或者将 \(d\) 和 \(a_i\) 进行异或,目标是使得最后的结果恰好等于某个数。
现在小明和小红都在玩这个游戏,前者选择 \(d=x\) 作为起点,后者选择 \(d=x+3\),最终目标是 \(y\)。
题目数据保证两个人中有且仅有一个人在采取合适策略下能够达到目标,请输出这个人。
\(1\leq n \leq 10^5,0\leq x,a_i \leq 10^9,0\leq y\leq 10^{15}\)
题目信誓旦旦的保证了一定有一个人能达到目标,也没要求输出方案,那说明这题的判断条件可能没那么复杂,复杂的规则后面应该有一些共通点(主要是 Div2 的 B 题不应该出太逆天的难度)。
这题是个纯二进制技巧,如下:
- \(x\) 和 \(x+3\) 的奇偶性不一致
- 对数字 \(a,b\),\(a+b\) 和 \(a\oplus b\) 的奇偶性一致(异或本质上就是不进位加法)
综上,我们只考虑这些数的奇偶性,然后判断看看应该是谁会理论上胜利即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL n, x, y;
bool solve()
{
scanf("%lld%lld%lld", &n, &x, &y);
LL v = 0, a;
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a);
v ^= a;
}
return ((x ^ v) %2 == y % 2);
}
int main()
{
int T;
cin >> T;
while (T--) puts(solve() ? "Alice" : "Bob");
return 0;
}
C题 OKEA(构造)
给定 \(nk\) 个物品,价格分别为 \(1,2,3,\cdots,nk-2,nk-1,nk\)。
现在有一个柜子,一共 \(n\) 层,每层可以摆放 \(k\) 个物品。我们要把所有物品摆上去,并希望对于每一层,对于任意的 \(1\leq l\leq r \leq k\),区间 \([l,r]\) 上的物品的平均价格是一个整数。
请判断是否有这种摆放的可能,有的话则输出方案。
\(1\leq n,k\leq 500\)
\(k=1\) 时无需考虑,随便怎么输出都行,我们直接讨论 \(k>2\) 的情况。
观察(手玩)出了一种策略:我们将价值按照 \(n\) 取模的结果分成 \(n\) 类,同一类的物品按照价值大小来从左到右依次排列。手造几组样例都没啥问题,尝试数学证明:
对于第 \(i\) 行第 \(j\) 列的物品,价格应该为 \(i+(j-1)n\) 。
对于第 \(i\) 行上区间 \([l,r]\) 的物品,总价值为
前者显然可以直接被 \(r-l+1\) 整除,而后者的话,我们不可能指望 \(l+r-2\) 总为偶数,那只能靠判断 \(n\) 的奇偶性来判断了。
综上:
- \(k=1\) 的时候存在合法方案
- \(k\geq2\) 的时候,当 \(n\) 为偶数时存在合法方案,反之则不存在
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n, k;
scanf("%d%d", &n, &k);
if (k >= 2 && n % 2 == 1) puts("NO");
else {
puts("YES");
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= k; ++j)
printf("%d ", i + (j - 1) * n);
puts("");
}
}
}
int main()
{
int T;
cin >> T;
while (T--) solve();
return 0;
}
D题 Finding Zero(构造,交互)
这是一个交互题。
给定一个长度为 \(n\) 的数列 \(\{a_n\}\),我们只知道数列的长度,以及数列中仅有一个 0,其他的数都大于 0。
现在我们可以进行若干次询问,每次指定三个下标 \(i,j,k\),然后得到 \(\max(a_i,a_j,a_k)-\min(a_i,a_j,a_k)\) 的值。
我们至多进行 \(2n-2\) 次询问,询问结束后输出两个下标 \(x,y\),若 \(a_x=0\) 或 \(a_y=0\) 则成功。要求我们成功完成该流程。
\(4\leq n\leq 10^3,0\leq a_i\leq 10^9\)
这题属于逆天中的逆天,所以我没有啥思考,这里直接翻译(搬运)官方题解了。
线索1
题目要求找到两个可能的下标,这意味着我们不需要明确输出 0 的下标,相反,我们可以通过排除法,排除掉不可能为 0 的位置。
线索2
尝试在 \(n=4\) 的情况下找到答案。
我们记四个数从小到大排列,分别为 \(0=a<b\leq c\leq d\)
那么记 \(f(i)\) 为没有选择 \(i\) 的情况下得到的询问值,那么有:\(f(1)=d-b,f(2)=d,f(3)=d,f(4)=c\),显然 \(f(2)\) 和 \(f(3)\) 是最大的。结合实际情况,我们不难得到一个推论:四个数的时候进行该操作,倘若下标为 \(i\) 和 \(j\) 的 \(f\) 值最大,说明 \(a_i\) 和 \(a_j\) 必然不是 0。
线索3
尝试将其进行推广。
假设前四个数没有 0,那么 \(f(1)=d-b,f(2)=d-a,f(3)=d-a,f(4)=c-a\) 。
讲道理,\(f(2),f(3)\) 是最大的,那么下标 \(2,3\) 的数都不为 0。如果 \(f(2)=f(3)=f(4)\),那说明 \(c=d\),那么下标为 4 的数也不为 0。\(f(1)=f(2)=f(3)=f(4)\) 时则说明四个数都不为 0。也就是说,每次进行这一轮流程,至少可以排除两个非 0 的数。
那么当 \(n\) 为偶数的时候,我们就可以按照上面的步骤进行操作:开一个集合,先将开头四个数放进去,然后每次排除两个数之后再加进去两个,最后剩下来的两个就是疑似为 0 的坐标,总操作次数为 \(C_4^3+\frac{n-4}{2}*4=2n-4\) 。
当 \(n\) 为奇数的时候,上面这个方法不再一定有效(到最后集合剩下三个数,没办法排除)。所以我们直接从之前排除掉的数里面再选一个放进去即可,总操作次数为 \(C_4^3+\frac{n-5}{2}*4+C_4^3=2n-2\)。
#include<bits/stdc++.h>
using namespace std;
int n, possible;
int query(vector<int> &vec, int x) {
//ask
cout << "?";
for (int a : vec) if (a != x) cout << " " << a;
cout << endl;
//get
int res;
cin >> res;
return res;
}
void solve(vector<int> &vec) {
vector<pair<int, int> > score;
for (int i = 0; i < 4; ++i)
score.push_back(make_pair(query(vec, vec[i]), vec[i]));
sort(score.begin(), score.end());
int x = score[2].second, y = score[3].second;
possible = x;
vector<int> res;
for (int a : vec)
if (a != x && a != y) res.push_back(a);
vec.clear();
for (int a : res) vec.push_back(a);
}
int main()
{
int T;
cin >> T;
while (T--) {
vector<int> vec;
cin >> n;
vec.push_back(1), vec.push_back(2);
for (int i = 3; i < n; i += 2) {
vec.push_back(i), vec.push_back(i + 1);
solve(vec);
}
if (n % 2 == 1) {
vec.push_back(n), vec.push_back(possible);
solve(vec);
}
cout << "! " << vec[0] << " " << vec[1] << endl;
}
return 0;
}