@description@
求一棵无根树上本质不同的独立集的个数 mod 10^9 + 7。
我们称两个独立集 A, B 是不同的,当前仅当:
(1)存在一种方案,将树中的结点重新标号后,在 A 中出现的任意一条边在 B 中也应该出现。
(2)在满足条件(1)的前提下,以同样的重标号方式,如果 x 在 A 中属于独立集,在 B 中也应该属于独立集。
输入格式
第一行有一个正整数 n,表示结点数。结点从 1 编号到 n。
接下来 n - 1 行,每行两个整数 v, u,表示 v,u 两点之间有一条边。
输出格式
输出一行表示答案 mod 10^9 + 7。
样例输入1
1
样例输出1
2
样例输入2
6
1 2
1 3
1 4
4 5
4 6
样例输出2
9
数据范围与约定
对于 100% 的数据,n <= 500000。
@solution@
如果没有本质相同的要求,直接树形 dp 就完事儿了。
问题说的是无根树,我们可以转成更方便的有根树来做(包括树形 dp 也是在有根树上做)。
记点 rt 为根。
如果在重新标号后 rt 的位置不变,那么依据题目所说,与 rt 相连的点依然与 rt 相连。也就是说,我们只是将 rt 的子树重排,并且只会把同构的子树互相换位置。
同理对于这棵有根树中的每个点,我们都只会改变它同构的子树之间的顺序,而不会破坏父子关系。
在以上条件满足的情况下,我们就可以进行计数了。
还是使用 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 为根得到的有根树应该是同构的。
可以联想到有关树同构的另一个套路:重心。
重心作为整棵树中的特征点,只会在有两个重心的时候可能与另一个重心同构。
假如有两个重心,它们必然相邻。我们可以在重心之间插入一个虚点(但其实不用实际插入一个虚点),最后特判一下虚点就好了。这个虚点就成了唯一的重心。
那么以重心为根,就可以保证根不会被重编号成另外的数。
@accepted code@
#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]); }
@details@
感觉看网上的题解。。。好像为什么要以重心都解释得很简略。。。
自闭了好久才明白为什么要以重心为根 QAQ。。。