小星星 子集反演 容斥

2022-02-02 04:21:55 字數 3462 閱讀 7771

小 y 是乙個心靈手巧的女孩子,她喜歡手工製作一些小飾品。她有 \(n\) 顆小星星,用 \(m\) 條彩色的細線串了起來,每條細線連著兩顆小星星。

有一天她發現,她的飾品被破壞了,很多細線都被拆掉了。這個飾品只剩下了 \(n-1\) 條細線,但通過這些細線,這顆小星星還是被串在一起,也就是這些小星星通過這些細線形成了樹。小 y 找到了這個飾品的設計圖紙,她想知道現在飾品中的小星星對應著原來圖紙上的哪些小星星。如果現在飾品中兩顆小星星有細線相連,那麼要求對應的小星星原來的圖紙上也有細線相連。小 y 想知道有多少種可能的對應方式。

只有你告訴了她正確的答案,她才會把小飾品做為禮物送給你呢。

第一行包含 \(2\) 個正整數 \(n,m\),表示原來的飾品中小星星的個數和細線的條數。

接下來 \(m\) 行,每行包含 \(2\) 個正整數 \(u,v\),表示原來的飾品中小星星 \(u\) 和 \(v\) 通過細線連了起來。這裡的小星星從 \(1\) 開始標號。保證 \(u\neq v\) ,且每對小星星之間最多只有一條細線相連。

接下來 \(n-1\) 行,每行包含 \(2\) 個正整數 \(u,v\) ,表示現在的飾品中小星星 \(u\) 和 \(v\) 通過細線連了起來。保證這些小星星通過細線可以串在一起。

輸出共 \(1\) 行,包含乙個整數表示可能的對應方式的數量。

如果不存在可行的對應方式則輸出 \(0\) 。

4 3

1 21 3

1 44 1

4 24 3

6
對於 \(100\%\) 的資料,\(n\leqslant 17\),\(m\leqslant \frac \)。

首先考慮樸素狀壓。我們要求的答案是這棵樹有多少中在圖上的節點標號對映方案,所以我們設 \(f[i][j][s]\) 表示將 \(i\) 節點對映為 \(j\) 節點,其子樹內的點使用的對映集合為 \(s\) 的方案數,答案顯然就是 \(\sum^_f[1][i][u]\) ,表示 \(1\) 對映為 \(i\) ,且子樹對映為全集的方案數。轉移的時候注意一下包含與不包含關係的判斷就行了。

#includeusing namespace std;

const int l = 1 << 20;

char buffer[l],*s,*t;

#define gc (s == t && (t = (s = buffer) + fread(buffer,1,l,stdin),s == t) ? eof : *s++)

inline int read()

#define rint register int

#define rll register long long

#define ll long long

const int maxn = 18;

ll ans,f[maxn][maxn][1int head[maxn],tot;

int n,m;

int siz[maxn];

inline void add(rint x,rint y)

inline void dfs(rint x,rint fa)}}

} siz[x] += siz[v]; }}

int main()

for(rint i = 1;i < n;++i)

rint mx = (1 << n) - 1;

for(rint i = 0;i <= mx;++i)

g[cnt].push_back(i);//計算每個個數下都有哪些狀態。

} dfs(1,0);

for(rint i = 1;i <= n;++i)

printf("%lld\n",ans);

return 0;

}

顯然這個暴力不可用(因為陣列開太大 mle 了),開小點應該還能過一些點。

我們繼續考慮對這個暴力狀壓進行優化。本題的關鍵點就在於要求對映集合不能有重複的,那麼我們直接去除這個限制。欽定有且僅有集合 \(s\) 能夠出現在對映中。所以我們可以設 \(f(s)\) 為所有點對映恰好是集合 \(s\) 的情況。\(g(s)\) 為所有點對映最多為 \(s\) 的情況,那麼我們就可以得到如下式子:

\[g(s) = \sum_f(t)

\]證明:顯然。\(t\) 是 \(s\) 的子集,所以 \(g(s)\) 為 \(s\) 集合使用不一定全的情況,所以就等於所有子集使用完全的情況求和。

然後利用子集反演,得到:

\[f(s)=\sum_(-1)^\times g(t)

\]答案就是 \(f(全集)\) 。

然後我們對上邊的狀壓進行修改,用於求出 \(g(s)\) ,重新定義 \(f[i][j][s]\) 為 \(i\) 對映為 \(j\) ,使用集合最大為 \(s\) 的方案,其轉移就可以這樣:

\[f[x][j][s]=\prod _\}} (\sum_f[v][t][s])

\]最終得到

\[g(s)=\sum _ f[1][j][s]

\]在這裡由於狀態不會瞎變,所以我們改為列舉所有狀態,然後把第三維壓掉就行了。

然後開始亂七八糟根據一堆式子求個和就行了。**卡卡常,跑過毫無壓力。

#includeusing namespace std;

const int l = 1 << 20;

char buffer[l],*s,*t;

#define gc (s == t && (t = (s = buffer) + fread(buffer,1,l,stdin),s == t) ? eof : *s++)

#define rint register int

#define rll register long long

#define reg register

#define ll long long

#define read() ()

const int maxn = 18;

ll ans,f[maxn][maxn];//壓掉狀態那一維,因為列舉狀態即可。

struct nodee[maxn<<2];

int head[maxn],tot;

int n,m;

int vec[maxn][maxn];

int jl[maxn],cnt[1<>1] + (i & 1);//計算當前狀態的元素個數

jl[0] = 0;rll tmp = 0;

for(rint j = 1;j <= n;++j)if(i & (1 << (j - 1)))jl[++jl[0]] = j;//記錄集合元素個數以及元素

dfs(1,0);

for(rint j = 1;j <= jl[0];++j)tmp += f[1][jl[j]];//求和

ans += ((n - cnt[i]) & 1) ? -tmp : tmp;//根據子集反演的式子求和

} printf("%lld\n",ans);

return 0;

}

容斥 ZJOI2016 小星星

小y是乙個心靈手巧的女孩子,她喜歡手工製作一些小飾品。她有n顆小星星,用m條彩色的細線串了起來,每條細線連著兩顆小星星。有一天她發現,她的飾品被破壞了,很多細線都被拆掉了。這個飾品只剩下了n?1條細線,但通過這些細線,這顆小星星還是被串在一起,也就是這些小星星通過這些細線形成了樹。小y找到了這個飾品...

ZJOI2016 小星星(容斥 dp)

題意相當於給一棵樹重新賦予彼此不同的編號,要求樹上相鄰的兩個節點在給定的另外乙個無向圖中也存在邊相連。n很小,但列舉階乘肯定是會 的。發現編號彼此不同對統計答案的影響太大了,我們可以嘗試先讓編號可以重複,但是限制可以選用的編號集,即o 2 n 列舉n個數的子集,然後容斥一下答案。可選用的編號集合確定...

bzoj4455 容斥原理 DP 小星星

description 小y是乙個心靈手巧的女孩子,她喜歡手工製作一些小飾品。她有n顆小星星,用m條彩色的細線串了起來,每條細 線連著兩顆小星星。有一天她發現,她的飾品被破壞了,很多細線都被拆掉了。這個飾品只剩下了n?1條細線,但 通過這些細線,這顆小星星還是被串在一起,也就是這些小星星通過這些細線...