【HDU 5909】Tree Cutting

题目描述:

  给你一棵n个节点的带点权树。每个点的点权都小于m,m为某个2的整次方数,求树上所有联通块点集点权异或和为0-m-1的各有多少个。

 对于15%的数据n<=18;

对于另外20%的数据:n<=30;

对于另外25%的数据:n,m<=300;

对于100%的数据,n,m<=1024;

15%做法:

搜索,暴力枚举点集,判断是否是联通块,是就算贡献,这里就不细讲了。

35%做法:

注意到要求是联通块,所以搜索选点的时候加点优化,保证每一步都是联通的即可。

60%做法:

考虑在树上dp,任意选根,设f【i】【j】表示在i的子树中选取联通块,且异或和为j的方案数,g【i】【j】为在i的子树中选取联通块,必定选点i,且异或和为j的方案数。可以发现,对于i的子树考虑,f【i】【j】=g【i】【j】+f【k1】【j】+f【k2】【j】+f【k3】【j】......;其中(k1,k2,k3....为i的所有儿子),那么怎么求g【i】【j】呢,考虑合并答案:


如图,考虑将i的子树的答案一个个合并到i里面,我们按照k1,k2,k3的顺序合并答案,例如合并完k2子树的答案时, g【i】【j】暂时存的值就是选i点,以及可以在k1,k2两棵子树选点的构成的联通块的方案(不考虑在子树k3中的选点情况)。而合并答案的时候,就要在原来的基础上加上在子树k3一定选的方案数:

就这样合并答案即可,时间复杂度:

100%的做法:

我们考虑点分治,将每次要操作的重心为根造出dfs序,然后求强制过根的联通块方案数,然后分成子树分治,显然,算完强制过根的方案后,再算子树内的方案,子树内的方案是必定不过根的,所以可以不重不漏的计算,接下来的难点就在于怎么进行dp。我们可以考虑将联通块的点按dfs序取点,然后对于f【i】【j】表示现在以dfs序为i的点为决策点,异或和为j的方案数。dfs序为i的点一共只有两种情况,选和不选,由于强制过根,如果不选的话,它的子树也不能选,也就是决策点可以转移到f【i+size【sequence【i】】】【j】上(其中sequence【i】为dfs序为i的节点的编号,而size存的是对应编号的节点的子树的大小):




这样,我们发现,每个点只有两种决策,也就是说每个点只会转移两个方向,转移过程的时间复杂度是的,而点分治只需要递归logn层,所以总的时间复杂度:

                                                     

                                                               推荐番:《干物妹!小埋》

上代码(^-^)

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int pan[3005],to[3005],pp[3005],nex[3005],ans[3005],siz[3005],line[3005],v[3005],visit[3005];
int dp[1050][1050],ma,mi,p,mod,n,m,x,y,t,wei;
void dfs(int k,int sum)
{
	pan[k]=1;siz[k]=1;
	int pu=pp[k],ma=0;
	while (pu>0)
	{
		if (pan[to[pu]]==0 && visit[to[pu]]!=1)
		{
			dfs(to[pu],sum);siz[k]+=siz[to[pu]];
			ma=max(ma,siz[to[pu]]);
		}
		pu=nex[pu];
	}
	ma=max(ma,sum-siz[k]);
	if (ma<mi){mi=ma;wei=k;}
}
void dfs2(int k)
{
	pan[k]=0;siz[k]=1;p++;line[p]=k;
	int pu=pp[k];
	while (pu>0)
	{
		if (pan[to[pu]]==1 && visit[to[pu]]!=1)
		{
			dfs2(to[pu]);siz[k]+=siz[to[pu]];
		}
		pu=nex[pu];
	}
}
void zhao(int k,int large)
{
	mod=1000000007;
	mi=1000000007;p=0;
	dfs(k,large);
	dfs2(wei);
	for (int i=2;i<=large+1;i++)
	{
		for (int j=0;j<m;j++)
		{
			dp[i][j]=0;
		}
	}
	dp[2][v[wei]]=1;
	for (int i=2;i<=large;i++)
	{
		for (int j=0;j<m;j++)
		{
			dp[i+1][j^v[line[i]]]=(dp[i+1][j^v[line[i]]]+dp[i][j])%mod;	
			dp[i+siz[line[i]]][j]=(dp[i+siz[line[i]]][j]+dp[i][j])%mod;
		}
	}
	for (int i=0;i<m;i++)
	{
		ans[i]=(ans[i]+dp[large+1][i])%mod;
	}
	int ww=2,dang=0,yao[1024],din[1024];
	visit[wei]=1;
	while (ww<=large)
	{
		dang++;yao[dang]=line[ww];din[dang]=siz[line[ww]];
		ww+=siz[line[ww]];
	}
	for (int i=1;i<=dang;i++)zhao(yao[i],din[i]);
}
int main()
{
	cin>>t;
	for (int o=1;o<=t;o++)
	{
		for (int i=1;i<=n;i++)visit[i]=0;
	cin>>n>>m;p=0;
	for (int i=0;i<m;i++)ans[i]=0;
	for (int i=1;i<=n;i++)pp[i]=0;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&v[i]);
	}
	for (int i=1;i<n;i++)
	{
		scanf("%d %d",&x,&y);
		p++;to[p]=y;nex[p]=pp[x];pp[x]=p;
		p++;to[p]=x;nex[p]=pp[y];pp[y]=p;
	}
	zhao(1,n);
	for (int i=0;i<m-1;i++)
	{
		printf("%d ",ans[i]);
	}
	printf("%d",ans[m-1]);
	printf("\n");
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值