zoukankan      html  css  js  c++  java
  • @bzoj


    @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
    ", sub(mul(f[2][p], f[2][q]), mul(f[1][p], f[1][q])));
    		else printf("%d
    ", add(mul(f[0][p], f[1][p]), C(f[0][p], 2)));
    	}
    	else dfs(p, 0), printf("%d
    ", f[2][p]);
    }
    

    @details@

    感觉看网上的题解。。。好像为什么要以重心都解释得很简略。。。
    自闭了好久才明白为什么要以重心为根 QAQ。。。

  • 相关阅读:
    省队集训Ⅱ-Day5
    省队集训Ⅱ-Day4
    省队集训Ⅱ-Day3
    省队集训Ⅱ-Day2
    省队集训Ⅱ-Day1
    并查集 Pro
    树上带修: 莫队Ⅳ
    树上骗分: 莫队Ⅲ
    带修骗分: 莫队Ⅱ
    骗分带师: 莫队Ⅰ
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11807226.html
Copyright © 2011-2022 走看看