# A. Hide and Seek
看了好久没看懂题 /kk
后来意识到应该是只能移动到代币初始所在单元格的两旁的单元格,而不是当前所在单元格的两旁。
然后这题就简单很多了。
观察样例解释,发现只要起点或者终点有一个不一样就算不同方案。
换句话说,我们只考虑移动一步的情况,即向左,向右或者不动。
不难证明,这样所有的起点终点情况都会被考虑到。
考虑枚举每一个单元格,判断当前单元格能否作为:
- 起点以及终点。
- 起点在左边时的终点。
- 起点在右边时的终点。
如何判断呢?
我们记录一下每个点被询问到的最小时间和最晚时间。
- 如果代币在当前点一直不动,那么只有询问中没有出现当前点时合法,即:
- 如果 ,那么 可以作为起点为 时的终点。
- 同理,如果 ,那么 可以作为起点为 时的终点。
然后记录一下答案即可。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define int long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1e5 + 10; | |
int n, k, ans; | |
int a[N], mx[N], mn[N]; | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
n = read(), k = read(); | |
memset(mx, -1, sizeof(mx)); | |
memset(mn, 0x3f, sizeof(mn)); | |
for(int i = 1; i <= k; ++i){ | |
a[i] = read(); | |
mx[a[i]] = max(mx[a[i]], i), mn[a[i]] = min(mn[a[i]], i); | |
} | |
for(int i = 1; i <= n; ++i){ | |
if(i > 1 && mn[i] > mx[i - 1]) ans++; | |
if(i < n && mn[i] > mx[i + 1]) ans++; | |
if(mn[i] > mx[i]) ans++; | |
} | |
write(ans), puts(""); | |
return 0; | |
} |
# B. Chladni Figure
一个比较显然的结论:
如果旋转 个点可以和原图形重合,那么旋转 个点也可以和原图形重合。
显然旋转一圈(即旋转 个点)可以使得图形和原图形重合,因此我们只需要枚举 的约数并旋转即可。
判断是否重合只需要判断每条线段旋转之后的位置是否有线段即可。
线段的位置开个 记录一下就行。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define fi first | |
#define se second | |
#define po(x, d) ((x + d - 1) % n + 1) | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1e5 + 10; | |
typedef pair<int, int> P; | |
int n, m; | |
set <P> S; | |
inline void solve(int d){ | |
int flag = 1; | |
for(auto x : S){ | |
P p = P(po(x.fi, d), po(x.se, d)); | |
if(p.fi > p.se) swap(p.fi, p.se); | |
if(!S.count(p)) {flag = 0; break;} | |
} | |
if(flag) puts("Yes"), exit(0); | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
n = read(), m = read(); | |
for(int i = 1; i <= m; ++i){ | |
int a = read(), b = read(); | |
if(a > b) swap(a, b); | |
S.insert(P(a, b)); | |
} | |
for(int i = 2; i * i <= n; ++i) | |
if(n % i == 0) solve(i), solve(n / i); | |
solve(1); | |
return puts("No"), 0; | |
} |
# C. Thanos Nim
博弈题。
先上结论:
堆石子中最小值出现次数小于等于 时,先手胜;反之,先手败。
证明:
如果开始时最小值出现次数小于等于 ,那么先手可以选择其他 堆不是最小值的石堆,从中取出若干石头使得它们变成最小值。
然后最小值出现次数大于 ,并且轮到后手。
后手最多只能选择 堆石堆,使得它们成为最小值,然后就又轮到先手。
再考虑一下什么情况下游戏结束。
最小值为 0,且 0 的个数大于 。
再根据上面所说的过程,后手操作完最多产生 个 0,然后先手再操作即可获得胜利。
如果开始时最小值出现次数大于 ,那么先手选择的石堆中必定包括最小石堆,导致最小值减小,从而最小值出现次数必然小于等于 ,然后后手必胜。
所以结论成立。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 55; | |
int n, mn = 60, cnt; | |
int a[N]; | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
n = read(); | |
for(int i = 1; i <= n; ++i) mn = min(mn, a[i] = read()); | |
for(int i = 1; i <= n; ++i) | |
if(a[i] == mn) cnt++; | |
puts(cnt <= n / 2 ? "Alice" : "Bob"); | |
return 0; | |
} |
# D. Palindrome XOR
题目中有个很奇怪的性质:
的首个字符为 1。
又因为 ,可得 的第一位为 1, 的第一位为 0。
因此我们 的最高位已经确定了, 的最高位还没有确定,考虑先枚举 的最高位是第几位。
然后 和 的最高位是第几位都确定了,其他位如何处理呢?
考虑转化为图论模型。
现在的问题是:有 个点分为两组,每组 个点,每个点都有一个点权为 或 ,有一些点的点权已经确定,有的点权还没确定。有一些限制条件形如 和 权值必须相同,或必须不同,求合法的不同点权方案数。
建一张有 个点的图,其中有两个点 ,点权分别为 ,如果有 两个点权值相同,那么 之间连权值为 的边,反之连权值为 的边。
具体来说:
- 先向 连权值为 的边,表示 点权不同。
- 根据 连边:
- 若 ,表示 点权不同, 之间连权值为 的边。
- 若 同理,令 之间连权值为 的边。
- 若 ,那么不连边。
- 若 点权固定,那么向它的点权连权值为 的边。
- 对于回文要求: 向 连权值为 的边,表示 和 权值相同, 同理。
建好图之后,我们可以把边权为 的边缩起来,这一步用并查集实现,然后就只剩下了边权为 的边。
对于每个联通块,显然都必须是二分图,否则不合法,判二分图直接黑白染色即可。
最后的贡献就是 , 为合法联通块个数,减 是因为 那两个点所在联通块的权值已经固定了,没有贡献。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 2010; | |
const int mod = 998244353; | |
int n, tot; | |
char s[N]; | |
int a[N], b[N]; | |
int id[N], id_a[N], id_b[N]; | |
int fa[N], siz[N], col[N]; | |
vector <int> g[N]; | |
inline int find(int x){ | |
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); | |
} | |
inline void Union(int x, int y){ | |
x = find(x), y = find(y); | |
if(x == y) return; | |
if(siz[x] > siz[y]) swap(x, y); | |
fa[x] = y, siz[y] += siz[x]; | |
} | |
inline void add(int x, int y){ | |
g[find(x)].pb(find(y)), g[find(y)].pb(find(x)); | |
} | |
bool flag; | |
inline void dfs(int x, int c){ | |
col[x] = c; | |
for(auto y : g[x]){ | |
if(col[y] == col[x]) return flag = 1, void(); | |
if(!col[y]) dfs(y, 3 - c); | |
} | |
} | |
int px[N], ans; | |
inline void init(){ | |
id[0] = ++tot, id[1] = ++tot; | |
for(int i = 1; i <= n; ++i) | |
id_a[i] = ++tot, id_b[i] = ++tot; | |
px[0] = 1; | |
for(int i = 1; i <= tot; ++i) px[i] = 2ll * px[i - 1] % mod; | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
scanf("%s", s + 1), n = strlen(s + 1); | |
init(); | |
for(int st = 2; st <= n; ++st){ | |
for(int i = 1; i <= tot; ++i) fa[i] = i, siz[i] = 1, col[i] = 0, g[i].clear(); | |
Union(id_a[st], id[1]), Union(id_b[1], id[1]); | |
for(int i = 1; i < st; ++i) Union(id_a[i], id[0]); | |
for(int i = st; i <= n; ++i) Union(id_a[i], id_a[n - (i - st + 1) + 1]); | |
for(int i = 1; i <= n; ++i){ | |
Union(id_b[i], id_b[n - i + 1]); | |
if(s[i] == '0') Union(id_a[i], id_b[i]); | |
} | |
add(id[0], id[1]); | |
for(int i = 1; i <= n; ++i) | |
if(s[i] == '1') add(id_a[i], id_b[i]); | |
int cnt = 0; flag = 0; | |
for(int i = 1; i <= tot; ++i) | |
if(find(i) == i && !col[i]){ | |
dfs(i, 1); | |
if(flag) break; | |
cnt++; | |
} | |
if(!flag) ans = (ans + px[cnt - 1]) % mod; | |
} | |
write(ans), puts(""); | |
return 0; | |
} |
# E. Rainbow Coins
观察到最多 次询问,那跟序列长度肯定是没什么关系了。
我们肯定尽可能的把每次询问都问满,那么比较显然地询问:
然后相邻的两个硬币的颜色关系就已经知道了,我们再把颜色相同的合并一下,合并之后的颜色块为 。
现在相邻两个 的颜色不同。
继续来询问:
此时我们就已经可以推出所有的颜色了,具体来说,设三种颜色分别为 :
- 设 。
- 如果 ,那么 ,反之 。
- 同理。
- 此时我们知道了 这样 4 个一组之内的颜色关系。
- 第 4 次询问后我们又把所有组连在了一起:
- 如果 ,那么 ,反之 。
- 同理。
- 然后我们就把 和 连起来了,后面的依次推过去即可。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1e5 + 10; | |
int T, n, cnt; | |
int ans[N], now[N]; | |
vector <int> A, B, C; | |
char s[N]; | |
typedef pair<int, int> P; | |
map <P, int> col; | |
inline void Clear(){ | |
memset(ans, 0, sizeof(ans)); | |
A.clear(), B.clear(), cnt = 0; | |
col.clear(); | |
} | |
inline void ask(){ | |
if(A.empty()) return; | |
printf("Q %d ", (int)A.size()); | |
for(int i = 0; i < (int)A.size(); ++i) | |
printf("%d %d ", A[i], B[i]); | |
puts(""); | |
fflush(stdout); | |
scanf("%s", s); | |
for(int i = 0; i < (int)A.size(); ++i) | |
col[P(A[i], B[i])] = (s[i] == '1'); | |
A.clear(), B.clear(); | |
} | |
inline void add(int st, int ed, int l){ | |
for(int i = st; i <= ed; i += l) A.pb(i), B.pb(i + (l >> 1)); | |
} | |
inline void add_now(int st, int ed, int l){ | |
for(int i = st; i <= ed; i += l) A.pb(now[i]), B.pb(now[i + (l >> 1)]); | |
} | |
inline void print(vector <int> A){ | |
for(auto x : A) printf("%d ", x); | |
puts(""); | |
} | |
inline void solve(){ | |
scanf("%d", &n); | |
add(1, n - 1, 2), ask(); | |
add(2, n - 1, 2), ask(); | |
now[++cnt] = 1; | |
for(int i = 1; i < n; ++i) | |
if(!col[P(i, i + 1)]) now[++cnt] = i + 1; | |
add_now(1, cnt - 2, 4), add_now(2, cnt - 2, 4), ask(); | |
add_now(3, cnt - 2, 4), add_now(4, cnt - 2, 4), ask(); | |
ans[now[1]] = 1; | |
if(cnt > 1) ans[now[2]] = 2; | |
for(int i = 3; i <= cnt; ++i){ | |
if(col[P(now[i - 2], now[i])]) ans[now[i]] = ans[now[i - 2]]; | |
else ans[now[i]] = 6 - ans[now[i - 2]] - ans[now[i - 1]]; | |
} | |
A.clear(), B.clear(), C.clear(); | |
for(int i = 1, c; i <= n; ++i){ | |
if(ans[i]) c = ans[i]; | |
if(c == 1) A.pb(i); | |
if(c == 2) B.pb(i); | |
if(c == 3) C.pb(i); | |
} | |
printf("A %d %d %d\n", (int)A.size(), (int)B.size(), (int)C.size()); | |
print(A), print(B), print(C); | |
fflush(stdout); | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
scanf("%d", &T); | |
while(T--) Clear(), solve(); | |
return 0; | |
} |
# F. Zigzag Game
太过神仙,还不会。