bzoj 2067 [ Poi 2004 ] SZN —— 二分
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2067
问题1:贪心考虑,应该是每个点的儿子尽量两两配对,如果剩一个就和自己合并向上,所以 ans = 1 + ∑(1<= i <= n ) (deg[i] - 1)/2
问题2:二分最长线段的长度,设 f[x] 表示自己带着的链的长度(即儿子中剩下的那一个带来的长度),判断是否满足条件即可;
如果当前节点有偶数个儿子,那么加一个 f 值为0的,进行二分;
注意根要单独判断,因为不能向上带了。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define mid ((l+r)>>1) using namespace std; int const xn=10005; int n,hd[xn],ct,to[xn<<1],nxt[xn<<1],f[xn],ans,deg[xn],a[xn],cnt; void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;} int rd() { int ret=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();} while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return f?ret:-ret; } bool ck2(int pos,int lim) { for(int l=1,r=cnt;l<=r;l++,r--) { if(l==pos)l++; if(r==pos)r--; if(a[l]+a[r]>lim)return 0; } return 1; } bool ck(int x,int fa,int lim) { for(int i=hd[x],u;i;i=nxt[i]) { if((u=to[i])==fa)continue; if(!ck(u,x,lim))return 0; } cnt=0; for(int i=hd[x];i;i=nxt[i])if(to[i]!=fa)a[++cnt]=f[to[i]]+1; if(x==1) { sort(a+1,a+cnt+1); if(cnt%2)f[x]=a[cnt],cnt--; for(int l=1,r=cnt;l<=r;l++,r--) if(a[l]+a[r]>lim)return 0; return f[x]<=lim; } if(cnt%2==0)a[++cnt]=0; sort(a+1,a+cnt+1); int l=1,r=cnt,ret=-1; while(l<=r) { if(ck2(mid,lim))ret=mid,r=mid-1; else l=mid+1; } if(ret==-1)return 0; f[x]=a[ret]; return f[x]<=lim; } int main() { n=rd(); int ans1=1; for(int i=1,x,y;i<n;i++) { x=rd(); y=rd(); deg[x]++; deg[y]++; add(x,y); add(y,x); } for(int i=1;i<=n;i++)ans1+=(deg[i]-1)/2; printf("%d ",ans1); int l=0,r=n; while(l<=r) { if(ck(1,0,mid))ans=mid,r=mid-1; else l=mid+1; } printf("%d\n",ans); return 0; }