[CF979E]Kuro and Topological Parity[题解]
Kuro and Topological Parity
题目描述
给定一个 \(n\) 个点的图,每个点有黑白两种颜色(可能存在没有颜色的点,你可以将其涂成黑色或白色)。同时,你可以在这张图上加入一些边,要求不存在重边和自环且加入的边必须从编号小的点指向编号大的点。
称一条好的路径经过的点黑白相间。值得注意的是,每个点自身也是一条好的路径。如果对于 \(p\in \{0,1 \}\),一张图好的路径的数量满足 \(\mod 2\) 刚好等于 \(p\),则称这张图是好的图。
给定 \(n\) 个点的部分情况,求这 \(n\) 个点可以组成的好的图的数量,答案取模 \(1000000007\)。
数据范围
\(n\leq 50,p\in \{0, 1 \}\)
对于每个点的情况,用一个整数 \(a_i\) 表示,若 \(a_i = 0\),则这个点为黑点,若 \(a_i = 1\),则为白点,若 \(a_i = -1\),则这个点的颜色不确定。
分析
考虑对于一条长度为 \(d\) 的合法路径,在其末尾添加一个节点,使其仍然合法。那么,包括新加入的点自身,我们会得到 \(d+1\) 条新的合法路径。由于我们最后要对总路径进行奇偶讨论,那么这里对 \(d\) 我们也进行奇偶讨论。我们不妨设出一下四种表示,奇黑、奇白、偶黑、偶白。
其中,例如奇黑的定义为,有奇数条以该点为结尾的合法路径的点,且该点颜色为黑。另外三种表示的定义类似。
注意到 \(n\leq 50\),考虑设状态 \(f[i][a][b][c]\) 表示当枚举到第 \(i\) 个点时,奇黑、奇白、偶黑的数量分别为 \(a\),\(b\),\(c\),而偶白的数量我们显然可以通过这四种状态求得,考虑转移。
假设转移一个白点(黑点的情况类似)。
显然,新增加的合法路径条数一定和这个点有关。一开始,如果不向其进行任何连边,则增加的合法路径数是奇数,考虑四种点向其进行连边时可能会出现的情况(注意下述新增合法路径不包括其本身形成的合法路径):
-
奇、偶白向其连边,不会新增加合法路径,也就不会改变奇偶。
-
偶黑向其连边也只会增加偶数条合法,只有奇黑才有可能改变其奇偶。假设有 \(j\) 个奇黑,显然必须要经过奇数个奇黑才能改变奇偶,显然,有 \(2^{j - 1}\) 次方中不同的取法取得奇数个奇黑,亦有 \(2^{j-1}\) 次方中不同的取法取得偶数个奇黑。其他点由于不影响,随便取,共 \(2^{i - 1 - j}\) 种可能,则转移则针对这两种奇偶转变讨论即可。
显然,这样做的复杂度是 \(O(n^4)\) 的,足以通过此题。但 \(da32s1da\) 大佬给出了一种 \(O(n)\) 的做法。
我们优化我们的状态为 \(f[i][j][a][b]\) 表示枚举到第 \(i\) 个点,好的路径条数的奇偶性,是否存在奇白,是否存在奇黑。
因为事实上,每次更新我们只需要知道奇黑或者奇白是否存在即可,因为只要其存在,那我们所有的转移就会对半分。在上述的讨论中不难发现这一点,另一个理解是:我们可以从奇黑和奇白中挑选出一个控制奇偶性。
具体的转移还需要关注该点插入后,会变成奇黑(白)还是偶黑(白)。具体的,还是以插入白点为例。
-
如果不存在奇黑,显然这个白点只能作为奇白存在,得到的贡献即位上一个状态乘上 \(2^{i - 1}\),即所有边任意连。
-
如果存在奇黑,则这个白点作为奇白、偶白存在的情况对半分,分别得到上一个状态乘上 \(2^{i - 2}\) 的贡献,具体原因如上,下不赘述;。
这样做的复杂度会有一个 \(8\) 的常数,但基本忽略不计,即 \(O(n)\)。
\(code\)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 60, lim = 50, mod = 1e9 + 7;
inline int read()
{
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int n, p, ans;
int fac[N], col[N], f[N][2][2][2];
signed main()
{
n = read(), p = read();
for(register int i = 1; i <= n; i++) col[i] = read();
fac[0] = 1;
for(register int i = 1; i <= lim; i++) fac[i] = (fac[i - 1] << 1) % mod;
f[0][0][0][0] = 1;
for(register int i = 1; i <= n; i++){
for(register int j = 0; j <= 1; j++){ //枚举奇偶性
for(register int a = 0; a <= 1; a++){ //枚举奇白是否存在
for(register int b = 0; b <= 1; b++){ //枚举奇黑是否存在
if(col[i] != 0){ //这个点可能是白点
if(b){ //奇数黑点存在
f[i][j][a][b] = (f[i][j][a][b] + f[i - 1][j][a][b] * fac[i - 2] % mod) % mod;
f[i][j ^ 1][a | 1][b] = (f[i][j ^ 1][a | 1][b] + f[i - 1][j][a][b] * fac[i - 2] % mod) % mod;
}
else f[i][j ^ 1][a | 1][b] = (f[i][j ^ 1][a | 1][b] + f[i - 1][j][a][b] * fac[i - 1] % mod) % mod;
}
if(col[i] != 1){ //这个点可能是黑点
if(a){ //奇数白点存在
f[i][j][a][b] = (f[i][j][a][b] + f[i - 1][j][a][b] * fac[i - 2] % mod) % mod;
f[i][j ^ 1][a][b | 1] = (f[i][j ^ 1][a][b | 1] + f[i - 1][j][a][b] * fac[i - 2] % mod) %mod;
}
else f[i][j ^ 1][a][b | 1] = (f[i][j ^ 1][a][b | 1] + f[i - 1][j][a][b] * fac[i - 1] % mod) % mod;
}
}
}
}
}
int ans = 0;
for(register int a = 0; a <= 1; a++)
for(register int b = 0; b <= 1; b++) ans = (ans + f[n][p][a][b]) % mod;
printf("%lld\n", ans);
return 0;
}