Codeforces GYM102307 G. Graduation 题解

120 阅读2分钟

题目链接

思路

一门课只能作为另外至多一门课的先修课,将每一门课作为一个节点,从一门课连一条有向边到其先修课,那么必定构成一个森林。问题就转化为给定一棵树,每次取kk个叶结点,问多少次可以取完。注意一个叶结点被取过之后他的父亲节点并不能马上变为叶结点,要等取完这一轮才可以(因为修完xx的先修课的下一学期才能修xx)。
将所有出度为00的元素维护到一个集合qq中,也就是叶子集合。每次从中取kk个元素,并分别将这些元素父亲的出度减一。若其父亲出度已经变成00,就将其父亲扔到另一个集合redred当中,等取完这一轮再把这些父亲扔到qq中,就可以保证一门课不会和其先修课在同一学期修完。
考虑怎样安排取叶子结点的顺序才能让取的次数最少。优先取深度较大的显然可以获得最优解。假如现在在较深的深度取了恰好kk个点,如果把其中一些点换成深度较小的点,在当前来看并没有影响,但当我们在以后的一轮并没有取够kk个点,需要这些深度小的点来补的时候,就有可能找不到足够的点补足空缺,造成取点次数的浪费。因此将qq换为优先队列,以depdep为关键字进行维护,先取深度大的点。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u) for(int i=head[u];i;i=e[i].nxt)
using namespace std;
const int N=1E4+10;
int n,k,tot,cnt,ans;
int head[N],out[N],red[N];
struct Node{
	int out,fa,dep;
	bool operator < (const Node &tmp) const
	{
		return dep<tmp.dep;
	}
}a[N];
struct Edge{
	int nxt,to;
}e[N];
priority_queue<Node> q;
inline int read()
{
	int ret=0;char ch=getchar();
	while(ch<'0' || ch>'9')
		ch=getchar();
	while(ch>='0' && ch<='9')
	{
		ret=ret*10+ch-'0';
		ch=getchar();
	}
	return ret;
}
inline void add_edge(int u,int v,int flag)
{
	e[++tot].nxt=head[u];e[tot].to=v;head[u]=tot;
	if(flag)
		add_edge(v,u,0);
}
int dfs(int u)
{
	a[u].dep=a[a[u].fa].dep+1;
	bl(u)
	{
		int v=e[i].to;
		dfs(v);
	}
}
int main()
{
	n=read();k=read();
	rep(i,1,n)
	{
		a[i].fa=read();
		add_edge(a[i].fa,i,0);
		++a[a[i].fa].out;
	}
	rep(i,1,n)
	{
		if(!a[i].fa)
			dfs(i);
	}
	int ok=0;
	rep(i,1,n)
	{
		if(a[i].out==0)
			q.push(a[i]);
	}
	while(ok!=n)
	{
		rep(i,1,cnt)
			q.push(a[red[i]]);
		cnt=0;
		int num=0;
		while(!q.empty())
		{
			Node cur=q.top();q.pop();
			++ok;
			int fa=cur.fa;
			--a[fa].out;
			if(a[fa].out==0)
				red[++cnt]=fa;
			++num;
			if(num==k)
			{
				break;
			}
		}
		++ans;
	}
	cout<<ans<<endl;
}