CF1385F Removing Leaves 拓扑排序

题意

给定一棵树,现在定义一次操作为"选定拥有同样父亲的 \(k\) 个叶节点,并将这 \(k\) 个叶节点一起删去。问最多能够进行多少次操作。

范围&性质: \(1\le k\le n\le 2\times 10^5\)

分析:

由于这是一颗无根树,所以我们无法求出DFS每个点的儿子和父亲

那么我们换个角度考虑,每次被删除的点有什么特点,我们发现它们的度数都是1

也就是说我们按照拓扑排序的方法将度数为1的点删掉,同时一个点能成为叶子的前提条件有两个:

  1. 它的儿子都能删掉(直接或间接成为叶子)
  2. 它的儿子的个数为 \(k\) 的倍数(否则它的儿子删不完全)

对于根节点要特殊处理,因为别的点都有一个父亲,但根节点没有,所以最后统计答案时要将根节点的度数 \(+1\)

代码:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 2e5+5;
	int t,n,k,ans,cnt=0,idx,st;
	int head[maxn],deg[maxn],val[maxn];
	bool vis[maxn];
	struct edge
	{
		int to,nxt;
	}e[maxn<<1];
	queue<int> q;
	
	void add(int u,int v)
	{
		e[++cnt].to=v;
		e[cnt].nxt=head[u];
		head[u]=cnt;
		deg[v]++;
	}
	
	void toposort()
	{
		while(!q.empty()) q.pop();
		for(int i=1;i<=n;i++) if(deg[i]==1) q.push(i);
		while(!q.empty())
		{
			int u=q.front();q.pop();
			if(vis[u]) continue;
			vis[u]=true;
			for(int i=head[u];i;i=e[i].nxt)
			{
				int v=e[i].to;
				if(!vis[v])
				{
					val[v]++;
					if(val[v]%k==0&&val[v]==deg[v]-1) q.push(v);	
				}
			}
		}
		for(int i=1;i<=n;i++) ans+=val[i]/k;
	}
	
	void work()
	{
		int a,b;
		t=read();
		while(t--)
		{
			cnt=0;ans=0;idx=0;
			for(int i=1;i<=n;i++) head[i]=deg[i]=val[i]=0,vis[i]=false;
			n=read();k=read();
			for(int i=1;i<n;i++)
			{
				a=read();b=read();
				add(a,b);add(b,a);
			}
			if(k==1)
			{
				printf("%d\n",n-1);
				continue;
			}
			toposort();
			printf("%d\n",ans);
		}
	
	}

}

int main()
{
	zzc::work();
	return 0;
}
posted @ 2020-11-16 11:23  youth518  阅读(99)  评论(0编辑  收藏  举报