求一棵無根樹上本質不一樣的獨立集的個數 mod 10^9 + 7。spa
咱們稱兩個獨立集 A, B 是不一樣的,當前僅當:
(1)存在一種方案,將樹中的結點從新標號後,在 A 中出現的任意一條邊在 B 中也應該出現。
(2)在知足條件(1)的前提下,以一樣的重標號方式,若是 x 在 A 中屬於獨立集,在 B 中也應該屬於獨立集。code
輸入格式
第一行有一個正整數 n,表示結點數。結點從 1 編號到 n。
接下來 n - 1 行,每行兩個整數 v, u,表示 v,u 兩點之間有一條邊。
輸出格式
輸出一行表示答案 mod 10^9 + 7。ip
樣例輸入1
1
樣例輸出1
2
樣例輸入2
6
1 2
1 3
1 4
4 5
4 6
樣例輸出2
9get
數據範圍與約定
對於 100% 的數據,n <= 500000。it
若是沒有本質相同的要求,直接樹形 dp 就完事兒了。io
問題說的是無根樹,咱們能夠轉成更方便的有根樹來作(包括樹形 dp 也是在有根樹上作)。class
記點 rt 爲根。
若是在從新標號後 rt 的位置不變,那麼依據題目所說,與 rt 相連的點依然與 rt 相連。也就是說,咱們只是將 rt 的子樹重排,而且只會把同構的子樹互相換位置。
同理對於這棵有根樹中的每一個點,咱們都只會改變它同構的子樹之間的順序,而不會破壞父子關係。stream
在以上條件知足的狀況下,咱們就能夠進行計數了。
仍是使用 dp,只是 dp 的時候咱們對於重構的子樹總體轉移(用樹哈希判一下重構便可)。重構
假如對於某一類子樹共 x 個,這一類子樹的方案數爲 k。它的貢獻爲可重集的組合 C(x+k-1, x)。
至關於方程 p1 + p2 + ... + pk = x 的解,其中 pi 表示選擇第 i 種方案的子樹個數。
注意這裏的 k 是取餘事後的,可是能夠運用 lucas 證實這裏的取模不會影響最終結果。
這裏的組合數能夠直接暴算,x 的總和爲 O(n)。
假如從新編號後,rt 的位置變到了另外一個結點。能夠發現這種狀況下變化很大,不是很好計數。
那麼怎麼才能恰當地選擇 rt,使得 rt 不可能改變到另外一個結點呢?
注意到假如點 x 被重編號成 rt,那麼以點 x 爲根與以點 rt 爲根獲得的有根樹應該是同構的。
能夠聯想到有關樹同構的另外一個套路:重心。
重心做爲整棵樹中的特徵點,只會在有兩個重心的時候可能與另外一個重心同構。
假若有兩個重心,它們必然相鄰。咱們能夠在重心之間插入一個虛點(但其實不用實際插入一個虛點),最後特判一下虛點就行了。這個虛點就成了惟一的重心。
那麼以重心爲根,就能夠保證根不會被重編號成另外的數。
#include <cstdio> #include <vector> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; typedef unsigned long long ull; typedef pair<ull, int> pr; const int MAXN = 500000; const int MOD = int(1E9) + 7; int inv[MAXN + 5]; inline int add(int a, int b) {return (a + b) % MOD;} inline int mul(int a, int b) {return 1LL*a*b % MOD;} inline int sub(int a, int b) {return add(a, MOD-b);} inline int dv(int a, int b) {return mul(a, inv[b]);} struct edge{ int to; edge *nxt; }edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=edges; void addedge(int u, int v) { edge *p = (++ecnt); p->to = v, p->nxt = adj[u], adj[u] = p; p = (++ecnt); p->to = u, p->nxt = adj[v], adj[v] = p; } int siz[MAXN + 5], fa[MAXN + 5], n; int get_siz(int x, int f) { fa[x] = f, siz[x] = 1; for(edge *p=adj[x];p;p=p->nxt) if( p->to != f ) siz[x] += get_siz(p->to, x); return siz[x]; } int hvy[MAXN + 5]; int getG(int x, int tot) { int ret = -1; hvy[x] = tot - siz[x]; for(edge *p=adj[x];p;p=p->nxt) if( p->to != fa[x] ) { int t = getG(p->to, tot); if( ret == -1 || hvy[ret] > hvy[t] ) ret = t; if( siz[p->to] > hvy[x] ) hvy[x] = siz[p->to]; } if( ret == -1 || hvy[ret] > hvy[x] ) ret = x; return ret; } ull rd[MAXN + 5]; void init() { srand(20041112^n); for(int i=1;i<=n;i++) rd[i] = (((ull)rand() << 16 | rand()) << 16 | rand()) << 16 | rand(); inv[1] = 1; for(int i=2;i<=n;i++) inv[i] = MOD - 1LL*inv[MOD % i]*(MOD/i)%MOD; } int C(int n, int m) { int ret = 1; for(int i=1;i<=m;i++) ret = dv(mul(ret, n + m - i), i); return ret; }// C(n + m - 1, m) vector<pair<ull, int> >v; ull h[MAXN + 5]; int f[3][MAXN + 5]; void dfs(int x, int fa) { siz[x] = h[x] = 1; for(edge *p=adj[x];p;p=p->nxt) { if( p->to == fa ) continue; dfs(p->to, x), h[x] += rd[siz[p->to]] * h[p->to], siz[x] += siz[p->to]; } v.clear(); for(edge *p=adj[x];p;p=p->nxt) { if( p->to == fa ) continue; v.push_back(make_pair(h[p->to], p->to)); } sort(v.begin(), v.end()); f[0][x] = f[1][x] = 1; int lst = 0; for(int i=1;i<v.size();i++) { if( v[i].first != v[i-1].first ) { f[0][x] = mul(f[0][x], C(f[2][v[lst].second], i - lst)); f[1][x] = mul(f[1][x], C(f[0][v[lst].second], i - lst)); lst = i; } } if( v.size() ) { f[0][x] = mul(f[0][x], C(f[2][v[lst].second], v.size() - lst)); f[1][x] = mul(f[1][x], C(f[0][v[lst].second], v.size() - lst)); } f[2][x] = add(f[0][x], f[1][x]); } int main() { scanf("%d", &n), init(); for(int i=1;i<n;i++) { int u, v; scanf("%d%d", &u, &v); addedge(u, v); } int p = getG(1, get_siz(1, 0)); if( fa[p] && hvy[p] == hvy[fa[p]] ) { int q = fa[p]; dfs(p, q), dfs(q, p); if( h[p] != h[q] ) printf("%d\n", sub(mul(f[2][p], f[2][q]), mul(f[1][p], f[1][q]))); else printf("%d\n", add(mul(f[0][p], f[1][p]), C(f[0][p], 2))); } else dfs(p, 0), printf("%d\n", f[2][p]); }
感受看網上的題解。。。好像爲何要以重心都解釋得很簡略。。。 自閉了很久才明白爲何要以重心爲根 QAQ。。。