@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。。。

posted @ 2019-11-06 18:45  Tiw_Air_OAO  阅读(135)  评论(0编辑  收藏  举报