题目
题意
给出一棵以1为根节点的树,每秒可以依次,进行以下两种操作
Spreading,对于每个结点,如果他有儿子结点被感染,可以感染其另一个没有被感染的儿子结点.
Injection任意选择一个结点将其感染
求感染所有结点最少需要的时间
思路
我们可以二分答案mid,问题就变成了mid秒内能否感染所有结点.
首先Injection一定用于优先感染兄弟结点比较多的结点,这样可以充分利用Spreading,
我们可以结点按照兄弟的数量排序,然后优先感染兄弟多的结点.这样我们就知道了,第一秒被Injection的结点剩下的时间里可以被Spreading mid-1个兄弟,第二秒可以被Injection的结点可以被Spreading mid-2个兄弟,所以我们扫描一遍就可以知道还剩下多少个兄弟结点还没被感染,判断能否用剩下的Injection的操作将这些结点感染即可.
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int n;
int cnt;
int bro[maxn];
vector<int>G[maxn];
bool cmp(int a,int b)
{
return a>b;
}
bool check(int mid)
{
int remain=0;
for(int i=1,j=mid-1;i<=cnt;i++,j--)
{
remain+=max(0,bro[i]-j);
}
return mid-cnt>=remain;
}
void solve()
{
cin>>n;
for(int i=0;i<=n;i++)
G[i].clear();
for(int i=2;i<=n;i++)
{
int x;
cin>>x;
G[x].push_back(i);
}
cnt=1;
bro[1]=0;
for(int i=1;i<=n;i++)
{
if(G[i].size())
bro[++cnt]=G[i].size()-1;
}
sort(bro+1,bro+1+cnt,cmp);
int l=1,r=n;
int ans;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid))
{
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin>>t;
while(t--)
{
solve();
}
}