BZOJ 3162 独钓寒江雪 树同构+树形DP

题目大意:给定一棵树,求本质不同的独立集个数对1000000007取模后的值

首先独立集个数应该都会求吧- -

令f[x][0]为x这个点不选的独立集个数

f[x][1]为x这个点选的独立集个数

那么有f[x][0]=Σf[son[x]][0]+f[son[x]][1]

f[x][1]=Σf[son[x]][0]

但是现在要求本质不同

说到本质不同我们很容易想到群论 但是群论显然写不了- -

于是我们考虑对树进行一下处理

首先将树有根化

为了保证形态相同的子树在有根化之后形态依然相同,我们需要找到这棵树的重心

如果重心是两个点,就在这两个点之间添加一个点作为根

如果随便选择一个点作为根,后果就是形态相同的部分有根化之后形态不同- -

比如说过不去2 1 2这组样例- -

而如果选择了重心,由于两个形态相同的部分大小一定相同,故重心一定不在其中之一

接下来考虑对DP方程进行一些处理

比如说有三棵形态完全相同的树,每棵都有ABCD四种方案

那么我选AAB和选BAA是等价的

n个点,涂上m种颜色,那么本质不同的方案数为C(n+m-1,n)

现在的问题就是计算形态相同的子树的个数

我们可以设计一个Hash函数对每棵子树进行哈希

Hash函数越奇葩越好- - 太简单的会有BUG

比如我的方法:

首先对点设一个初值,将子树按照哈希值排序,每次执行:

for(i=1;i<=top;i++)
		(((hash[x]*=BASE)+=hash[stack[i]])^=hash[stack[i]])+=hash[stack[i]];

最好自行设计hash函数,但是必须保证形态相同的子树被Hash成相同的值,形态不同的子树被Hash成不同的值

然后就搞过了- - 随便写了一发RANK1了什么情况- - 一定是自然溢出比较快的缘故- -

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 500500
#define MOD 1000000007
#define ORIGIN 233
#define BASE 233333333
using namespace std;
struct abcd{
	int to,next;
}table[M<<1];
int head[M],tot=1;
int n,root,cgs[2],size[M];
unsigned long long hash[M];
long long inv[M],f[M][2],ans;
bool Compare(int x,int y)
{
	return hash[x] < hash[y];
}
void Add(int x,int y)
{
	table[++tot].to=y;
	table[tot].next=head[x];
	head[x]=tot;
}
void DFS(int x,int from)
{
	int i,flag=1;
	size[x]=1;
	for(i=head[x];i;i=table[i].next)
	{
		if(table[i].to==from)
			continue;
		DFS(table[i].to,x);
		size[x]+=size[table[i].to];
		if(size[table[i].to]<<1>n)
			flag=0;
	}
	if(n-size[x]<<1>n)
		flag=0;
	if(flag)
		(cgs[0]?cgs[1]:cgs[0])=x;
}
long long C(long long n,long long m)
{
	int i;
	long long re=1;
	for(n%=MOD,i=1;i<=m;i++)
		(re*=(n-i+1)*inv[i]%MOD)%=MOD;
	return re;
}
void Tree_DP(int x,int from)
{
	static int stack[M];
	int i,j,top=0;
	hash[x]=ORIGIN;
	for(i=head[x];i;i=table[i].next)
		if(table[i].to!=from)
			Tree_DP(table[i].to,x);
	for(i=head[x];i;i=table[i].next)
		if(table[i].to!=from)
			stack[++top]=table[i].to;
	sort(stack+1,stack+top+1,Compare);
	f[x][0]=f[x][1]=1;
	for(i=1;i<=top;i=j)
	{
		for(j=i+1;j<=top&&hash[stack[i]]==hash[stack[j]];j++);
		(f[x][0]*=C(j-i+f[stack[i]][0]+f[stack[i]][1]-1,j-i) )%=MOD;
		(f[x][1]*=C(j-i+f[stack[i]][0]-1,j-i) )%=MOD;
	}
	for(i=1;i<=top;i++)
		(((hash[x]*=BASE)+=hash[stack[i]])^=hash[stack[i]])+=hash[stack[i]];
}
void Linear_Shaker()
{
	int i;
	inv[1]=1;
	for(i=2;i<=n;i++)
		inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}
int main()
{
	int i,x,y;
	cin>>n;
	Linear_Shaker();
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		Add(x,y);Add(y,x);
	}
	DFS(1,0);
	if(cgs[1])
	{
		for(i=head[cgs[0]];i;i=table[i].next)
			if(table[i].to==cgs[1])
			{
				table[i].to=table[i^1].to=root=n+1;
				break;
			}
		Add(n+1,cgs[0]);
		Add(n+1,cgs[1]);
	}
	else
		root=cgs[0];
	Tree_DP(root,0);
	if(!cgs[1])
		ans=(f[root][0]+f[root][1])%MOD;
	else
	{
		x=cgs[0];y=cgs[1];
		if(hash[x]!=hash[y])
			ans=(f[x][0]*f[y][0]%MOD+f[x][1]*f[y][0]%MOD+f[x][0]*f[y][1]%MOD)%MOD;
		else
			ans=(f[x][0]*f[y][1]+C(f[x][0]+1,2) )%MOD;
	}
	cout<<ans<<endl;
	return 0;
}
时间: 2024-10-08 10:37:05

BZOJ 3162 独钓寒江雪 树同构+树形DP的相关文章

BZOJ 2878([Noi2012]迷失游乐园-树形DP+环加外向树+期望DP+vector的erase)

2878: [Noi2012]迷失游乐园 Time Limit: 10 Sec  Memory Limit: 512 MBSec  Special Judge Submit: 319  Solved: 223 [Submit][Status] Description 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩.进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点.m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1).小Z现在所在的大门也正好是

bzoj 3566: [SHOI2014]概率充电器 树形DP

首先普及一个概率公式 P(A+B)=P(A)+P(B)-P(AB) 题意:一些充电元件和导线构成一棵树,充电元件是否能充电有2种情况, 1.它自己有qi%的概率充电 2.与它相邻的元件通过导线给它充电(导线有p%的概率导通) 求最终充了电的元件的期望 题解:首先可以将元件能否充电分成3种情况考虑, 1.它自己给自己充好了电 2.它的儿子方向给它传送了电 3.它的父亲方向给它传送了电. 对于1,题目已经给出可以直接赋值, 对于2,可以通过一次树的深度遍历求得.pson[now]=pson[now]

[BZOJ 4033] [HAOI2015] T1 【树形DP】

题目链接:BZOJ - 4033 题目分析 使用树形DP,用 f[i][j] 表示在以 i 为根的子树,有 j 个黑点的最大权值. 这个权值指的是,这个子树内部的点对间距离的贡献,以及 i 和 Father[i] 之间的边对答案的贡献(比如这条边对黑点对距离和的贡献就是子树内部的黑点数 * 子树外部的黑点数 * 这条边的权值). 然后DFS来求,枚举 i 的每个儿子 j,现在的 f[i][] 是包含了 [1, j-1] 子树,然后两重循环枚举范围是 [1, j - 1] 的子树总 Size 和

CF809E Surprise me! 莫比乌斯反演、虚树、树形DP

传送门 简化题意:给出一棵\(n\)个点的树,编号为\(1\)到\(n\),第\(i\)个点的点权为\(a_i\),保证序列\(a_i\)是一个\(1\)到\(n\)的排列,求 \[ \frac{1}{n(n-1)} \sum\limits_{i=1}^n \sum\limits_{j=1}^n \varphi(a_ia_j) dist(i,j)\] 其中\(dist(i,j)\)为树上\(i,j\)两点的距离. 看到\(\varphi\)第一反应推式子 因为序列\(a_i\)是一个\(1\)到

BZOJ 2878: [Noi2012]迷失游乐园( 树形dp )

一棵树的话直接树形dp(求出往下走和往上走的期望长度). 假如是环套树, 环上的每棵树自己做一遍树形dp, 然后暴力枚举(环上的点<=20)环上每个点跑经过环上的路径就OK了. --------------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm&

青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 第一种,树形dp+LCA 比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2 然后找出所有互质的对数,然后对为1的数进行特殊处理.(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7) 对所有的非1质数对,采用离

BZOJ 2500 幸福的道路 树形DP+单调队列

题目大意:给定一棵树,令a[i]为从第i个节点出发的最长链,求a[i]中最长的区间,满足区间内最大值与最小值之差不超过m 读错题害死人,脑残害死人 求a[i]显然是树形DP 考虑从一个点出发的链可以从子节点走,也可以从父节点走 因此我们DP两次,第一次求出从子节点走的最长链,第二次求出从父节点走的最长链,两次取max就是答案 但是直接DP会有问题,因为从父节点走的最长链可能是从自己的子树出发的,这样就会走重 因此除记录从子节点出发的最长链外还要记录一个从另一个子节点出发的次长链,如果最长链长度相

bzoj 4033: [HAOI2015]T1(树形DP)

4033: [HAOI2015]T1 Time Limit: 10 Sec  Memory Limit: 256 MB Submit: 819  Solved: 375 [Submit][Status][Discuss] Description 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整 数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的 N-K个点染成白色 . 将所有点染色后,你会获得黑点两两之间的距 离加上白点两两之间的距离的和的受益.问受益最大值是多

【BZOJ2286】【SDOI2011】消耗战 LCA单调性(构建虚树)+树形DP

题解: 首先我们考虑每次都做一遍树形DP(树形DP自己脑补去,随便乱搞就过了). 显然这是TLE无疑的. 所以可以利用LCA单调性构建虚树. 思想: 我们发现每次树形DP有很多点用不到,但是却需要被扫过,让他们见鬼去吧! 实现: 我们只对每次扫的图插入本次询问需要的节点,以及它们的LCA. 这样询问了m个点,虚树就至多只需要2m个点(so quick). 而插入顺序上不妨利用LCA单调性来把点按dfs度排个序,然后挨个插入单调栈. 同时我们要保证单调栈维护的是一条链,也就是一旦不是链了,我们自然