@bzoj - 3162@ 独钓寒江雪
@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。。。