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

25 篇文章 1 订阅
23 篇文章 0 订阅

题目大意:给定一棵树,求本质不同的独立集个数对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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值