格子染色
题目链接:YBT2022寒假Day7 B
题目大意
有一个长度为 n 的纸条,然后有 k 种颜色,一些位置一开始已经染色颜色,保证相邻颜色不相同。
两人轮流操作,选一个一个未染色的地方染色,保证不能出现相邻位置颜色相同,谁无法操作谁就输了。
然后问你先手是否必胜。
思路
考虑对于 k k k 分类讨论。
首先如果 k > 2 k>2 k>2,那答案十分显然,肯定无论怎么搞格子都会被填满(一个点旁边顶多两个点),所以答案的 SG 函数就是还没有没填的格子数 % 2 \%2 %2 的结果。
然后我们考虑把每个数分割出的位置分别处理,答案异或起来。
然后先是稍微简单的
k
=
2
k=2
k=2,那这个我们要分情况讨论:
两边都没有数填,只有一边有数填了,两边都有数填了。
那有什么放的方法呢?
要么是放在最边边,要么是放在中间。
然后我们再看这三种情况:
两边都有数:我们可以推出 SG 函数在旁边两个相同时是
1
1
1,不同时是
0
0
0。
然后这里推一下,在长度为
1
1
1 的时候显然,然后看两种放的方法:
放在最边边,那两边颜色是否相同改变,然后是换到下一个人,所以没问题。
放在中间,分割成两个部分:如果原来颜色一样,那分割之后要么两个一样,要么两个都不一样,以后起来都是
0
0
0。如果颜色不一样,那分割之后肯定是一个一样一个不一样,以后起来还是
1
1
1。
然后就证好了。
有一边有这个时候可以证 SG 值就是区间的长度
S
S
S。
我们考虑在每个位置
i
(
1
⩽
i
⩽
S
)
i(1\leqslant i\leqslant S)
i(1⩽i⩽S) 都试一下(我们假定左边没有数),那再分类:
如果填的颜色跟右边的一样,那就是
(
i
−
1
)
⊕
0
(i-1)\oplus 0
(i−1)⊕0,包含了
0
∼
S
−
1
0\sim S-1
0∼S−1。
如果不一样,那就是
(
i
−
1
)
⊕
1
(i-1)\oplus1
(i−1)⊕1,然后会有
(
i
−
1
)
⊕
1
⩽
i
<
S
(i-1)\oplus1\leqslant i<S
(i−1)⊕1⩽i<S。
所以全部求 mex \text{mex} mex 的结果就是 S S S。
然后就是都没有限制:
考虑在位置
i
i
i 放会变成两个一边的,就是
(
i
−
1
)
⊕
(
n
−
i
)
(i-1)\oplus(n-i)
(i−1)⊕(n−i)。
显然这个东西只有可能在
n
n
n 是偶数的时候才会有
0
0
0 的结果。
然后因为只有这一个大区间,所以我们不用判断
n
n
n 是奇数的时候它是什么,直接判断就好了。
然后是最神奇的
k
=
1
k=1
k=1。
考虑 DP,设
f
i
f_i
fi 为一个长度为
i
i
i 的区间的 SG 函数。
然后也是两种选法,可以得出一个
O
(
n
2
)
O(n^2)
O(n2) 的 DP:
f
i
=
mex
{
f
i
−
2
,
f
j
⊕
f
i
−
3
−
j
(
0
⩽
j
⩽
i
−
3
)
}
f_{i}=\text{mex}\{f_{i-2},f_{j}\oplus f_{i-3-j}(0\leqslant j\leqslant i-3)\}
fi=mex{fi−2,fj⊕fi−3−j(0⩽j⩽i−3)}
然后发现这个似乎不太能优化了,于是我们考虑打标。
然后就会发现从第
52
52
52 项开始出现了一个周期为
34
34
34 的循环。
然后就可以只求前
85
85
85 位了。
(反正我是不是很懂这是怎么想到,怎么看出来的了)
代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n, k, a[200001];
int sg1[201];
int mex(vector <int> x) {
sort(x.begin(), x.end());
int re = 0;
for (int i = 0; i < x.size(); i++) {
int y = x[i];
if (y > re) return re;
else if (y == re) re++;
}
return re;
}
int main() {
// freopen("color.in", "r", stdin);
// freopen("color.out", "w", stdout);
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
if (k > 2) {
int num = 0;
for (int i = 1; i <= n; i++)
if (!a[i]) num++;
if (num & 1) printf("YES");
else printf("NO");
}
if (k == 2) {
bool all = 1;
for (int i = 1; i <= n; i++)
if (a[i]) {
all = 0; break;
}
if (all) {
if (n & 1) printf("YES");
else printf("NO");
return 0;
}
int L = 1, ans = 0;
for (int i = 1; i <= n; i++)
if (a[i]) {
int R = i - 1;
if (L > R) {L = i + 1; continue;}
if (L == 1 || R == 1) ans ^= R - L + 1;
else {
if (a[L - 1] == a[R + 1]) ans ^= 1;
else ans ^= 0;
}
L = i + 1;
}
int R = n;
if (L <= R) {
ans ^= R - L + 1;
}
if (ans) printf("YES");
else printf("NO");
}
if (k == 1) {
sg1[1] = 1;
for (int i = 2; i <= 85; i++) {
vector <int> tmp;
tmp.push_back(sg1[i - 2]);
for (int j = 0; j <= i - 3; j++)
tmp.push_back(sg1[j] ^ sg1[i - 3 - j]);
sg1[i] = mex(tmp);
}
int L = 1, ans = 0;
for (int i = 1; i <= n; i++)
if (a[i]) {
int R = i - 1;
int sz = R - L + 1 - 2; if (L == 1 || R == n) sz++; if (L == 1 && R == n) sz++;
if (sz <= 0) {L = i + 1; continue;}
if (sz < 52) ans ^= sg1[sz];
else ans ^= sg1[52 + ((sz - 52) % 34)];
L = i + 1;
}
int R = n;
int sz = R - L + 1 - 1; if (L == 1) sz++;
if (sz > 0) {
if (sz < 52) ans ^= sg1[sz];
else ans ^= sg1[52 + ((sz - 52) % 34)];
}
if (ans) printf("YES");
else printf("NO");
}
return 0;
}