HDU 6881 Tree cutting 点分治

题意:给你一棵树,删除最少的点数使得剩下的图仍然是树且直径小于等于k。

题解:枚举树的直径中心是点还是边,如果是点,按照点分治的方法,求出所有点的经过重心的,距离超过k/2的点数即可。如果是边,有一点需要处理,统计经过该边的在子树内的点数时,统一在这条边直接连在某重心下的时候统计,每个点在点分治的过程中都会被枚举到,每条边在父节点是根的时候加上子树内的贡献就可以了,对于这一点我的理解是因为边没法像点一样递归下去。(待补充……)

刚开始学点分治
分治的过程可以类比归并排序,分成不超过log层,每次分治都会使处理的规模/2,所以总的复杂度是nlogn
点分治大概有如下几个步骤
work(u) 对u这棵子树进行点分治
rt=findroot(u) 找u这棵子树的重心
dfs/bfs(rt) 重新对u这棵子树遍历,只不过此时的根节点为rt,相当于重构了这棵树,同时在遍历的时候要统计深度,sz等信息
cal(rt) 对这棵树递归,利用容斥原理计算贡献,进入一棵子树,就把这棵子树的贡献从整棵树的和里减掉

本题的小感悟:
1.每个点都会被当成重心计算一次,具体类比归并排序
2.在findroot的时候千万不要用全局变量记录rt,否则在回溯的时候rt就被修改了。
3.点分治约等于dfs套dfs
4.点数太大的时候改用bfs

借鉴了一下题解和std:

代码:

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+10;
typedef pair<int,int> P;
vector<P> e[N];
int T,n,half_e,half_v,cnt_v[N],cnt_e[N];
int fa[N],sum[N],sz[N],vis[N],k,mx[N];
int q[N];
int find_root(int s)
{
	sz[s]=1;mx[s]=0;fa[s]=-1;
	int h=0,t=0;
	q[t]=s;
	while(h<=t)
	{
		int u=q[h++];
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i].first;
			if(vis[v]||v==fa[u])continue;
			fa[v]=u;
			q[++t]=v;
			sz[v]=1;
			mx[v]=0;
		}
	}
	for(int i=t;i>=1;i--)
	{
		sz[fa[q[i]]]+=sz[q[i]];
		mx[fa[q[i]]]=max(mx[fa[q[i]]],sz[q[i]]);
	}
	int Max=sz[s],root=s;
	for(int i=0;i<=t;i++)
	{
		mx[q[i]]=max(mx[q[i]],sz[s]-sz[q[i]]);
		if(mx[q[i]]<Max)
		{
			Max=mx[q[i]];
			root=q[i];
		}
	}
	return root;
}
int mxdep,dep[N];
void bfs(int s)
{
	int h=0,t=-1;
	q[++t]=s;
	while(h<=t)
	{
		int u=q[h++];
		sum[dep[u]]++;
		mxdep=max(mxdep,dep[u]);
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i].first;
			if(vis[v]||v==fa[u])continue;
			fa[v]=u;
			dep[v]=dep[u]+1;
			q[++t]=v;
		}
	}
}
int subsum[N];
void bfs2(int s)
{
	int h=0,t=-1;
	q[++t]=s;
	while(h<=t)
	{
		int u=q[h++];
		subsum[dep[u]]++;
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i].first;
			if(vis[v]||v==fa[u])continue;
			q[++t]=v;
		}
	}
	int Max=dep[q[t]];
	for(int i=Max-1;i>=0;i--) subsum[i]+=subsum[i+1];
	for(int i=0;i<=t;i++)
	{
		int u=q[i];
		int dis=max(0,half_v-dep[u]+1);
		cnt_v[u]+=sum[dis]-subsum[dis];
		dis=max(0,half_e-dep[u]+2);
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i].first,id=e[u][i].second;
			if(v!=fa[u])continue;
			cnt_e[id]+=sum[dis]-subsum[dis];
			if(dep[u]==1)
				cnt_e[id]+=subsum[half_e+2];
		}
	}
	for(int i=0;i<=Max;i++)subsum[i]=0;
}
void work(int u)
{
	int root=find_root(u);
	fa[root]=-1;
	dep[root]=0;
	mxdep=0;
	bfs(root);
	for(int i=mxdep-1;i>=0;i--) sum[i]+=sum[i+1];
	cnt_v[root]+=sum[half_v+1];
	
	for(int i=0;i<e[root].size();i++)
	{
		int v=e[root][i].first;
		if(vis[v])continue;
		bfs2(v);
	}
	for(int i=0;i<=mxdep;i++) sum[i]=0;
	vis[root]=1;
	for(int i=0;i<e[root].size();i++)
	{
		int v=e[root][i].first;
		if(vis[v])continue;
		work(v);
	}
}
void init()
{
	half_e=(k-1)/2;
	half_v=k/2;
	for(int i=1;i<=n;i++)
	{
		e[i].clear();
		cnt_v[i]=cnt_e[i]=vis[i]=sz[i]=mx[i]=sum[i]=subsum[i]=fa[i]=0;
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		init();
		for(int i=1;i<n;i++)
		{
			int u,v;scanf("%d%d",&u,&v);
			e[u].push_back(P(v,i));
			e[v].push_back(P(u,i));
		}
		work(1);
		int ans=n;
		for(int i=1;i<=n;i++) ans=min(ans,cnt_v[i]);
		for(int i=1;i<=n-1;i++)ans=min(ans,cnt_e[i]);
		printf("%d\n",ans);
	}
	return 0;
}

posted @ 2020-09-04 14:33  JWizard  阅读(225)  评论(0)    收藏  举报