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