题解 P3000 【[USACO10DEC]牛的健美操Cow Calisthenics】

· · 题解

提供简洁的代码

"目前吸氧最优解"

这道题目总体不难

简单题意:将树中的某些边删去后,使各个连通块中最大直径最小

诶,"最大直径最小",这不是二分吗

于是我们想到,二分直径的最大长度,显然,由于答案合法的单调性,正确性是可以保证的

那么该如何判断是否可以满足呢?

我们设f[x]表示"从x出发的在x子树中路径的最大长度"

如果"f[x]"加上"max(f[to])"大于我们二分的长度,我们就断边。 (max(f[to]中除去f[x]所到的那个f[to],即最大的两个f[to]之和大于二分长度,下文皆是此意)

断哪条?

贪心的想,因为"断边后被断的边再也不会产生影响",所以我们只需要让留下来的边尽可能的小,就留下min(f[x]-1,f[to])+1

判断的正确性: 因为我们从下往上断边,而且是非断不可时才断,又能保证断边时一定留下最优解,所以判断是正确的。 ### 完整代码奉上: ```cpp #include<iostream> #include<cstdio> #include<ctype.h> using namespace std; inline int read(){ int x=0,f=0;char ch=getchar(); while(!isdigit(ch))f|=ch=='-',ch=getchar(); while(isdigit(ch))x=x*10+(ch^48),ch=getchar(); return f?-x:x; } struct Edge{ int next,to; }edge[200007]; int head[100007],cnt; int f[100007]; inline void add_edge(int from,int to){ edge[++cnt].next=head[from]; edge[cnt].to=to;head[from]=cnt; } int t,n,s;//t表示当前删边的个数 void dfs(int x,int fa,int maxl){ if(t>s)return;//t>s说明不能完成 int lx=0;//lx表示最大f[to] for(int i=head[x];i;i=edge[i].next){ int to=edge[i].to;if(to==fa)continue; dfs(to,x,maxl); if(lx+f[to]>maxl)++t,lx=min(lx,f[to]);//如果需要删边,就保留最小值 else lx=max(lx,f[to]);//否则就更新为最大值 } f[x]=lx+1; } inline bool check(int maxl){ t=0;dfs(1,0,maxl);//注意清空计数器t return t<=s; } int main(){ n=read(),s=read(); for(int i=1;i<n;++i){ int u=read(),v=read(); add_edge(u,v),add_edge(v,u); }//以上为读入建图 int l=0,r=n/s;//二分下界显然是0,上界其实只要到(n-s)/s向上取正(已经删去了s条边)。 while(l<=r){//普通二分 int mid=l+r>>1; if(check(mid))r=mid-1; else l=mid+1; } printf("%d\n",l);//输出答案 return 0;//好习惯 } ``` 对了,最后说一下二分 ```cpp if(check(mid))r=mid-1; else l=mid+1; ``` 很多人一直对此有疑惑 一般有三种,最常见的好像是$if(check(mid))$后记录一下$ans

这里证明一下我习惯写的

相反,因为在$check$不成立后$l$会$+1$,到最后的极限$l=r$时答案就是正确的