[Noip2018]赛道修建
一.前言
这题的部分分还是给的非常友好!STL真好用!题目链接
二.20pts
这是 (m=1) 的做法,只需要两遍dfs算树的直径就可以了,代码稍有注释。
int est,sume;
void dfs1(int x,int fa,int sumd){
if(sumd>sume)est=x,sume=sumd;//更新离他最远的点
for(int i=head[x];i;i=ne[i]){
if(to[i]==fa)continue;
dfs1(to[i],x,sumd+dis[i]);
}
}
if(m==1){
dfs1(1,0,0);
sume=0;
dfs1(est,0,0);//用离1最远的est再来一次
printf("%d",sume);
}
三.40pts
这是20分加上链的情况……
给出一种错误思路:直接总和除m(一个特别大就可以hack)
下面是正确的思路,二分一个最短长度埋伏他一手,然后贪心看最多能够凑出几个赛道来。
bool check(int x){
int cnt=0,sum=0;
m_for(i,1,n-1){
if(sum+a[i]>=x)sum=0,cnt++;//又凑出一段
else sum+=a[i];
}
return cnt>=m;
}
//链式向前星存储的,special1是判断是不是链
else if(!spe1){
m_for(i,1,n-1)
sume+=(a[i]=(dis[head[i]]!=a[i-1]?dis[head[i]]:dis[ne[head[i]]]));
//这里偷个懒把长度总和和求出到i+1的长度合在一起写了
int l=1,r=sume,ans=1;//二分上界为长度总和
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
}
三.55pts
就是 (a_i=1) 的菊花图,也是部分分的上限。对于菊花图,我们要么是连着1的两条边凑一起,要么一条边自成一家。这里先 sort 从大到小排个序,然后取前 (2m) 个来拼赛道,按照最大配最小的来。这样可以使得赛道的最短长度最大(当 (2m>n) 时相当于接上空气赛道,长度为0,也就是自成一家,不影响)
else if(!spe2){
for(int i=head[1];i;i=ne[i])a[to[i]]=dis[i];
sort(a+1,a+n+1,cmp);
int ans=1<<29;
for(int i=1;i<=m;++i)ans=min(ans,a[i]+a[(m<<1)-i+1]);
printf("%d",ans);
}
四.100pts
本片题解的重头戏,满分思路来啦!
首先还是二分长度埋伏他一手,凑出尽可能多的赛道。题目给出的大背景是一个树,我才用深搜,很显然的,遍历到一个点,我先让儿子们只凭借他们所在的子树凑出尽可能多的赛道来。必然的,儿子不可能将他们身上所有的边都用完,此时出现了若干条还没用的边,如图
现在在红点,让蓝点完成了任务,蓝点反馈说我还有三条黑边没有用上,请求父亲帮助!也许这三条黑边任意一条加上红边就可以凑出赛道,又或许加上红边还不行,还要和蓝边的兄弟再操作一手才行……
但是不管怎么样,这三条黑边都必须加上红边才可能有机会。并且在一条黑边获得宠幸后其他的黑边就再也没有机会了。本着贪心的原则,我们追求尽量多的赛道,那么肯定是从这三条黑边当中选一条最长的出来和红边相接。于是儿子做完任务后还需要返回一个最长的没有用到的假赛道。
接下来考虑如何算出只依靠自己的子树的赛道数。很显然,儿子们都反馈给我了一些最长的假赛道,我从中将假赛道接起来就行。还剩下的无用假赛道选一个最长的返回给父亲。如此,这题就做完了。
int dfs(int x,int fa,int &alf,int mid){
int ans=0;
multiset<int> s;//保存假赛道
for(int i=head[x];i;i=ne[i]){
if(to[i]==fa)continue;
int k=0;//儿子的最长假赛道
ans+=dfs(to[i],x,k,mid);//让儿子先做任务
k+=dis[i];//和红边配起来
if(k>=mid)ans++;//最好直接成为赛道
else s.insert(k);//不行就放入假赛道集合中
}
while(!s.empty()){
int k=*s.begin();//取一个最小假赛道
s.erase(s.begin());//直接扔掉
multiset<int>::iterator it=s.lower_bound(mid-k);//能配起来的最小假赛道
if(it!=s.end()){ans++;s.erase(it);}//找得到就拼起来,然后删掉
else alf=max(alf,k);//记录最长无用假赛道
}
return ans;
}
else{
dfs1(1,0,0);
sume=0;
dfs1(est,0,0);
int r=sume,l=1,ans;//二分上界为树的直径
while(l<=r){
int mid=(l+r)>>1;
int alf=0;
if(dfs(1,0,alf,mid)>=m)l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
}
最后将各个模块拼起来就行qwq