# A. Hide and Seek
看了好久没看懂题 /kk
后来意识到应该是只能移动到代币初始所在单元格的两旁的单元格,而不是当前所在单元格的两旁。
然后这题就简单很多了。
观察样例解释,发现只要起点或者终点有一个不一样就算不同方案。
换句话说,我们只考虑移动一步的情况,即向左,向右或者不动。
不难证明,这样所有的起点终点情况都会被考虑到。
考虑枚举每一个单元格,判断当前单元格能否作为:
- 起点以及终点。
- 起点在左边时的终点。
- 起点在右边时的终点。
如何判断呢?
我们记录一下每个点被询问到的最小时间和最晚时间。
- 如果代币在当前点一直不动,那么只有询问中没有出现当前点时合法,即:
- 如果 ,那么 可以作为起点为 时的终点。
- 同理,如果 ,那么 可以作为起点为 时的终点。
然后记录一下答案即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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
一个比较显然的结论:
如果旋转 个点可以和原图形重合,那么旋转 个点也可以和原图形重合。
显然旋转一圈(即旋转 个点)可以使得图形和原图形重合,因此我们只需要枚举 的约数并旋转即可。
判断是否重合只需要判断每条线段旋转之后的位置是否有线段即可。
线段的位置开个 记录一下就行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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,然后先手再操作即可获得胜利。
如果开始时最小值出现次数大于 ,那么先手选择的石堆中必定包括最小石堆,导致最小值减小,从而最小值出现次数必然小于等于 ,然后后手必胜。
所以结论成立。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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。
因此我们 的最高位已经确定了, 的最高位还没有确定,考虑先枚举 的最高位是第几位。
然后 和 的最高位是第几位都确定了,其他位如何处理呢?
考虑转化为图论模型。
现在的问题是:有 个点分为两组,每组 个点,每个点都有一个点权为 或 ,有一些点的点权已经确定,有的点权还没确定。有一些限制条件形如 和 权值必须相同,或必须不同,求合法的不同点权方案数。
建一张有 个点的图,其中有两个点 ,点权分别为 ,如果有 两个点权值相同,那么 之间连权值为 的边,反之连权值为 的边。
具体来说:
- 先向 连权值为 的边,表示 点权不同。
- 根据 连边:
- 若 ,表示 点权不同, 之间连权值为 的边。
- 若 同理,令 之间连权值为 的边。
- 若 ,那么不连边。
- 若 点权固定,那么向它的点权连权值为 的边。
- 对于回文要求: 向 连权值为 的边,表示 和 权值相同, 同理。
建好图之后,我们可以把边权为 的边缩起来,这一步用并查集实现,然后就只剩下了边权为 的边。
对于每个联通块,显然都必须是二分图,否则不合法,判二分图直接黑白染色即可。
最后的贡献就是 , 为合法联通块个数,减 是因为 那两个点所在联通块的权值已经固定了,没有贡献。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
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 次询问后我们又把所有组连在了一起:
- 如果 ,那么 ,反之 。
- 同理。
- 然后我们就把 和 连起来了,后面的依次推过去即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
scanf("%d", &T);
while(T--) Clear(), solve();
return 0;
}
# F. Zigzag Game
太过神仙,还不会。