[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 \{1,2 \}\)
對於每個點的情況,用一個整數 \(a_i\) 表示,若 \(a_i = 0\),則這個點為黑點,若 \(a_i = 1\),則為白點,若 \(a_i = -1\),則這個點的顏色不確定。
分析
考慮對於一條長度為 \(d\) 的合法路徑,在其末尾新增一個節點,使其仍然合法。那麼,包括新加入的點自身,我們會得到 \(d+1\) 條新的合法路徑。由於我們最後要對總路徑進行奇偶討論,那麼這裡對 \(d\) 我們也進行奇偶討論。我們不妨設出一下四種表示,奇黑、奇白、偶黑、偶白。
其中,例如奇黑的定義為,有奇數條以該點為結尾的合法路徑的點,且該點顏色為黑。另外三種表示的定義類似。
注意到 \(n\leq 50\)
假設轉移一個白點(黑點的情況類似)。
顯然,新增加的合法路徑條數一定和這個點有關。一開始,如果不向其進行任何連邊,則增加的合法路徑數是奇數,考慮四種點向其進行連邊時可能會出現的情況(注意下述新增合法路徑不包括其本身形成的合法路徑):
-
奇、偶白向其連邊,不會新增加合法路徑,也就不會改變奇偶。
-
偶黑向其連邊也只會增加偶數條合法,只有奇黑才有可能改變其奇偶。假設有 \(j\) 個奇黑,顯然必須要經過奇數個奇黑才能改變奇偶,顯然,有 \(2^{j - 1}\)
顯然,這樣做的複雜度是 \(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;
}