Codeforces Round #770 (Div. 2)
Codeforces Round #770 (Div. 2)
脑子逐渐好用起来(
A - Reverse and Concatenate
给定一个字符串,对它做 \(k\) 次操作,求可能的最终串,每次操作二选一:
- \(s\gets s+\text{rev}(x)\)
- \(s\gets \text{rev}(x)+x\)
显然做过一次操作之后,一个串就会变为回文串,之后怎么选都一样。
所以判断 \(k=0\) 与初始串是否为回文串就能简单判断了。
B - Fortune Telling
Alice 一开始有 \(x\),Bob 一开始有 \(x+3\),给他们一个长度为 \(n\) 的序列 \(A\)。
他们各自可以对自己的数 \(d\) 做如下变换(二选一),对每一个 \(i\in[1,n]\):
- \(d\gets d+a_i\)
- \(d\gets d\operatorname{xor}a_i\)
他们中恰有一个人最后可能得到给定的结果 \(g\),求是哪个人。
偏诈骗题。两者操作都是不改变奇偶性的,而 \(x\) 与 \(x+3\) 的奇偶性不同,没了。
C - OKEA
构造一个 \(n\times m\) 的矩阵,要求把 \(1\sim n\times m\) 都填进去。
且对于任意行的任意一个子串,设它长度为 \(L\),那么该子串的和被 \(L\) 整除。
或者输出无解。
比较适合手玩的简单构造题。当 \(n\) 为奇数时,只有 \(m=1\) 有解,平凡。
否则,一定有解,构造方法为 \(1,3,5,7,\cdots\) 连续填,奇数恰好填满一半,然后 \(2,4,6,8,\cdots\)。
D - Finding Zero
这是一道交互题。
给定一个有且仅有一个位置为 \(0\) 的序列 \(A\),要求用 \(\leq 2n-2\) 次询问和 \(1\) 次猜测得到该位置。
询问形如 \((i,j,k)\),将返回 \(\max(a_i,a_j,a_k)-\min(a_i,a_j,a_k)\)。
猜测形如 \((i,j)\),只要 \(a_i=0\) 或 \(a_j=0\) 即可。以上都满足 \(i\neq j\neq k\)。
考虑均摊用 \(2\) 个代价排除一个不可能的数字,就已经符合要求了。
简单思考,不难用 \(\binom{4}{3}=4\) 次排除 \(a,b,c,d\) 中至少 \(2\) 个数字。
最终统计答案时,可能碰到 \(n'=3\) 的情况,只需要随便找一个已经被排除的数字 \(d\) 来做类似的排除即可。
均摊下来确实是 \(2\) 次排掉 \(1\) 个数字,最坏情况下,当 \(n\) 为奇数,用 \(2n-2\),当 \(n\) 为偶数,用 \(2n-4\)。
赛时代码,丑陋不堪(
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 1010;
int n, a[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int Ask(int a, int b, int c) {
fflush(stdout);
printf("? %d %d %d\n", a, b, c);
fflush(stdout);
int now; cin >> now;
return now;
}
void Ans(int a, int b) {
if(! b) {b = (a == 1) ? 2 : 1;}
fflush(stdout);
printf("! %d %d\n", a, b);
fflush(stdout);
}
void Work() {
n = read(); int m = n;
rep(i, 1, n) a[i] = i;
while(n >= 4) {
int A = a[n], B = a[n - 1], C = a[n - 2], D = a[n - 3];
int S1 = Ask(A, B, C), S2 = Ask(A, B, D);
int S3 = Ask(A, C, D), S4 = Ask(B, C, D);
int Mx = max(max(S1, S2), max(S3, S4));
int num = (S1 == Mx) + (S2 == Mx) + (S3 == Mx) + (S4 == Mx);
if(num == 4) n -= 4;
else if(num == 3) {
n -= 3;
if(S1 != Mx)
a[n] = D, a[n + 1] = A, a[n + 2] = B, a[n + 3] = C;
else if(S2 != Mx)
a[n] = C, a[n + 1] = A, a[n + 2] = B, a[n + 3] = D;
else if(S3 != Mx)
a[n] = B, a[n + 1] = A, a[n + 2] = C, a[n + 3] = D;
else if(S4 != Mx)
a[n] = A, a[n + 1] = B, a[n + 2] = C, a[n + 3] = D;
}
else {
n -= 2;
if(S1 == Mx && S2 == Mx)
a[n] = A, a[n - 1] = B, a[n + 1] = C, a[n + 2] = D;
else if(S1 == Mx && S3 == Mx)
a[n] = A, a[n - 1] = C, a[n + 1] = B, a[n + 2] = D;
else if(S1 == Mx && S4 == Mx)
a[n] = B, a[n - 1] = C, a[n + 1] = A, a[n + 2] = D;
else if(S2 == Mx && S3 == Mx)
a[n] = A, a[n - 1] = D, a[n + 1] = B, a[n + 2] = C;
else if(S2 == Mx && S4 == Mx)
a[n] = B, a[n - 1] = D, a[n + 1] = A, a[n + 2] = C;
else if(S3 == Mx && S4 == Mx)
a[n] = C, a[n - 1] = D, a[n + 1] = A, a[n + 2] = B;
}
}
if(n == 3) {
int A = a[1], B = a[2], C = a[3], D = a[m];
int S1 = Ask(A, B, C), S2 = Ask(A, B, D);
int S3 = Ask(A, C, D), S4 = Ask(B, C, D);
int Mx = max(max(S1, S2), max(S3, S4));
int num = (S1 == Mx) + (S2 == Mx) + (S3 == Mx) + (S4 == Mx);
if(num == 3) {
n -= 3;
if(S1 != Mx) Ans(D, 0);
else if(S2 != Mx) Ans(C, 0);
else if(S3 != Mx) Ans(B, 0);
else if(S4 != Mx) Ans(A, 0);
}
else {
n -= 2;
if(S1 == Mx && S2 == Mx) Ans(A, B);
else if(S1 == Mx && S3 == Mx) Ans(A, C);
else if(S1 == Mx && S4 == Mx) Ans(B, C);
else if(S2 == Mx && S3 == Mx) Ans(A, D);
else if(S2 == Mx && S4 == Mx) Ans(B, D);
else if(S3 == Mx && S4 == Mx) Ans(C, D);
}
}
else if(n == 2) Ans(a[1], a[2]);
else Ans(a[1], 0);
}
int main() {
int T = read();
while(T --) Work();
return 0;
}
E - Fair Share
给定 \(n\) 个长度为偶数 \(n_i\) 的序列,要求将所有数字归入集合 \(L/R\) 中。
要求最终 \(L=R\),且每个序列中,恰有 \(n_i/2\) 个归入 \(L\) 中,另一半归入 \(R\) 中。
给出方案或者输出无解。
当某个数字全局出现奇数次时无解,否则一定有解。
因为很多偶数,很难不联想到图论中的欧拉回路,可以构造二分图:
- 左部点为:\(1\sim n\) 表示 \(n\) 个序列。
- 右部点为:\(1\sim t\) 表示(离散化后)全局出现过的权值。
- 连边:序列 \(i\) 中 \(j\) 出现过 \(k\) 次,就连 \(k\) 条 \((i,j)\) 边。
因为度数都是偶数,所以欧拉回路一定存在。
对于最终路径从左往右走的边,代表这个数归入 \(L\),反之归入 \(R\)。
每个序列恰有一半在 \(L\) 一半在 \(R\) 的条件自然满足了,因为左部点的入度 = 出度。
两个集合相等也自然满足了,因为右部点的入度 = 出度。
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 4e5 + 10, M = N << 1;
int n, m, t, a[N], b[N], c[N], num[N];
int cnt = 1, head[N], ans[M], frm[N];
bool vis[M], used[N];
struct Edge {int nxt, v;} e[M];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void Add(int u, int v) {
e[++ cnt] = (Edge) {head[u], v}, head[u] = cnt;
e[++ cnt] = (Edge) {head[v], u}, head[v] = cnt;
}
int stk[N], top;
void Euler(int s) {
stk[++ top] = s;
while(top) {
int u = stk[top], i = head[u];
used[u] = true;
while(i && vis[i]) i = e[i].nxt;
if(i) {
stk[++ top] = e[i].v;
ans[i] = (u <= n) ? 1 : 0;
ans[i ^ 1] = - 1;
vis[i] = vis[i ^ 1] = true;
head[u] = e[i].nxt;
}
else
top --;
}
}
int main() {
n = read();
rep(i, 1, n) {
a[i] = read();
rep(j, 1, a[i]) b[++ t] = c[t] = read();
}
sort(c + 1, c + t + 1);
m = unique(c + 1, c + t + 1) - (c + 1);
rep(i, 1, t)
b[i] = lower_bound(c + 1, c + m + 1, b[i]) - c,
num[b[i]] ++;
rep(i, 1, m) if(num[i] & 1) {puts("NO"); return 0;}
int o = 0;
rep(i, 1, n)
rep(j, 1, a[i]) Add(i, b[++ o] + n), frm[o] = cnt;
rep(i, 1, n) if(! used[i]) Euler(i);
puts("YES"), o = 0;
rep(i, 1, n) {
rep(j, 1, a[i]) {
int now = ans[frm[++ o]];
if(now == - 1) now = ans[frm[o] ^ 1];
putchar(now ? 'L' : 'R');
}
puts("");
}
return 0;
}
F - Fibonacci Additions
给定两个长度均为 \(n\) 的序列 \(A,B\) 和模数 \(p\),以下所有操作均在 \(\bmod p\) 意义下进行。
\(q\) 次修改,每次形如 \((l,r,A/B)\) 表示把 \([l,r]\) 区间加斐波那契,即 \(i\in [l,r],a_i\gets a_i+f_{i-l+1}\)。
\(f_1=f_2=1,f_i=f_{i-1}+f_{i-2}(i\geq 3)\)。
每次修改后需要回答 \(A,B\) 两个序列是否完全相同。
以为是什么神秘数据结构,看完题解发现是 \(O(n)\) 的。(sto DX 的 998ms 的分块 orz)
因为只需要维护相同性,所以没有那么繁琐。
设 \(c_i=a_i-b_i\),当它全为 \(0\) 时符合条件,再设 \(d_1=c_1,d_2=c_2-c_1,d_i=c_i-c_{i-1}-c_{i-2}(i\geq 3)\)。
不难发现 \(d\) 的全 \(0\) 性和 \(c\) 的全 \(0\) 性是等价的!
但是 \(d\) 很好维护,因为它的定义是与 \(fab\) 类似的,假设对 \(A\),做区间 \((l,r)\) 的修改,那么对 \(d\) 来说,只有:
- \(d_l\gets d_l+1\)
- \(d_{r+1}\gets d_{r+1}-f_{r-l+2}\)
- \(d_{r+2}\gets d_{r+2}-f_{r-l+1}\)
对 \(B\) 是类似的。这种针对特殊函数构造特殊差分的思想很是神秘(
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 3e5 + 10;
int n, q, P, cnt, a[N], b[N], d[N], f[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void Moi(int p, int v) {
if(p > n) return;
cnt -= (d[p] != 0);
d[p] = ((d[p] + v) % P + P) % P;
cnt += (d[p] != 0);
}
int main() {
n = read(), q = read(), P = read();
f[1] = f[2] = 1;
rep(i, 3, n + 5) f[i] = (f[i - 1] + f[i - 2]) % P;
rep(i, 1, n) a[i] = read();
rep(i, 1, n) b[i] = read();
rep(i, 1, n) d[i] = (a[i] - b[i] + P) % P;
per(i, n, 3) d[i] = ((d[i] - d[i - 1] - d[i - 2]) % P + P) % P;
if(n >= 2) d[2] = (d[2] - d[1] + P) % P;
rep(i, 1, n) cnt += (d[i] != 0);
while(q --) {
char opt[3]; scanf("%s", opt);
int l = read(), r = read();
if(opt[0] == 'A')
Moi(l, 1), Moi(r + 1, - f[r - l + 2]), Moi(r + 2, - f[r - l + 1]);
else
Moi(l, - 1), Moi(r + 1, f[r - l + 2]), Moi(r + 2, f[r - l + 1]);
puts(cnt ? "NO" : "YES");
}
return 0;
}