hdu6268 Master of Subgraph(点分治+bitset优化背包)

该博客介绍了如何使用点分治算法解决一个图论问题:给定一组点和一个阈值,判断是否存在连通块的权值和等于特定值。文章通过分析思路和给出代码实现,详细解释了如何通过找到重心并递归计算子树答案来降低复杂度,最终达到O(n*logn*m/64)的时间复杂度。
摘要由CSDN通过智能技术生成

题目

T(T<=15)组样例,每次给定n(n<=3e3)个点,

并且给定一个阈值m(m<=1e5),第i个点的权值为ai(ai<=1e5),

对于每个i属于[1,m],问图中是否存在连通块的权值和为i,

如果存在输出1,否则输出0

思路来源

https://www.cnblogs.com/cxhscst2/p/8857376.html

题解

题解就是这么暴力,点分治甚至就是个工具

因为最后答案的连通块一定必经某一个重心,

所以就用点分治统计每个重心能产生出来的答案

bitset<M>sum[i]表示必取i时的连通块哪些和是能凑出来的,

考虑到正常做背包的时候只能往背包里多加一个元素,所以v从u转移过来的时候就必取v即可

v搜完之后回溯到u时,u答案或上v的答案,

因为此时v并没有set(a[v]),所以v更新的答案,实际上是必取u时才能取到的答案,

考虑到最后答案必经枚举的重心u,所以点分治只是用重心的数量降了复杂度

复杂度大概O(n*logn*m/64)

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
typedef long long ll;
const int N=3e3+10,M=1e5+5;
int head[N],cnt;
struct edge{int v,nex;}e[2*N];
void add(int u,int v){e[++cnt]=edge{v,head[u]};head[u]=cnt;} 
bool vis[N];

bitset<M>sum[N],ans; 
int n,m,a[N],u,v;
int siz,f[N],sz[N],rt;

void init(int n){
	cnt=0;
	ans.reset();
	for(int i=1;i<=n;++i){
		sum[i].reset();
		vis[i]=head[i]=0;
	}
}
//找下一次的重心rt 
void getrt(int u,int fa,bool op){
	f[u]=0;sz[u]=1;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(v==fa||vis[v])continue;
		getrt(v,u,op);
		f[u]=max(f[u],sz[v]);
		sz[u]+=sz[v];
	}
	if(op){
		f[u]=max(f[u],siz-sz[u]);
		if(f[u]<f[rt])rt=u;
	}
}
//计算重心u到子树内每个点的距离 
void getdis(int u,int fa){
	sum[u]=sum[u]<<a[u];
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(v==fa||vis[v])continue;
		sum[v]=sum[u];
		getdis(v,u);
		sum[u]|=sum[v];
	}
	ans|=sum[u];
}
//计算以u为根的子树的答案
void cal(int u,int col){
	sum[u].reset();
	sum[u].set(0);
	getdis(u,0);
}
void dfs(int u){
	//每次用在u的子树里任取减去在v的子树里的答案
	//每次只计算 必经过u的答案 
	cal(u,0);
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(vis[v])continue;
		getrt(v,u,0);//获得正确的sz[v] 
		siz=sz[v];rt=0;
		getrt(v,u,1);
		dfs(rt);
	} 
 } 
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		init(n);
		for(int i=1;i<n;++i){
			scanf("%d%d",&u,&v);
			add(u,v);add(v,u);
		}
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
		}
		f[0]=siz=n;rt=0;
		getrt(1,0,1),dfs(rt);
		for(int i=1;i<=m;++i){
			if(ans.test(i))putchar('1');
			else putchar('0');
		}
		puts("");
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值