bzoj2067: [Poi2004]SZN
传送门:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2067
思路:首先第一问就是最少多少笔画完这个图,ans=1+Σ(deg[i]-1)/2
第二问显然可以二分+判定。
先二分最长长度限制lim
怎么判定呢?
对于每个点,把它子树所有点向上需要的答案统计出来到a[]中,如果子树个数是偶数,则额外加一个a[i]=0
然后对a排序,二分删掉a中的一个元素,从大到小匹配判断是否合法,如果任何方案都不合法,则是不合法的直接退出
如果弄到最后都合法,这个答案就合法,不过要注意判断根的时候如果子树是偶数个不能额外加元素,也不能二分判断,而要直接判断合法性,否则两个点会错
--http://lbn187.is-programmer.com/posts/179515.html#
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=10010,maxm=20010; using namespace std; int n,pre[maxm],now[maxn],son[maxm],deg[maxn],tot,res=1,cnt,va[maxn],a[maxn];//a[i]儿子节点连上来的链的长度,va[i]i下面的链的最大值 void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b,deg[b]++;} bool check(int x,int lim){ for (int l=1,r=cnt;l<=r;l++,r--){//贪心,第k大和第k小两两配对 if (l==x) l++; if (r==x) r--; if (a[l]+a[r]>lim) return 0; } return 1; } bool can(int x,int fa,int lim){ //printf("%d %d %d\n",x,fa,lim); int ans=0; for (int y=now[x];y;y=pre[y]) if (son[y]!=fa) if (!can(son[y],x,lim)) return 0; cnt=0; for (int y=now[x];y;y=pre[y]) if (son[y]!=fa) a[++cnt]=va[son[y]]+1; if (x==1&&cnt%2==0){//根节点因为上面没有点来check,所以为偶数时要判断两两配对方案是否合法 va[x]=0,sort(a+1,a+1+cnt); for (int l=1,r=cnt;l<r;l++,r--) va[x]=max(va[x],a[l]+a[r]); return va[x]<=lim; } if (!(cnt&1)) a[++cnt]=0;sort(a+1,a+1+cnt);//如果为偶数,加一个为0的点 for (int l=1,r=cnt,mid;l<=r;){ mid=(l+r)>>1; if (check(mid,lim)) ans=mid,r=mid-1;//二分不配对的节点 else l=mid+1; } if (!ans) va[x]=1e9;else va[x]=a[ans]; return va[x]<=lim; } int main(){ while (scanf("%d",&n)!=EOF){int ans=0; memset(now,0,sizeof(now)),memset(va,0,sizeof(va)),tot=0,res=1,memset(deg,0,sizeof(deg)),memset(a,0,sizeof(a)); for (int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x); for (int i=1;i<=n;i++) res+=(deg[i]-1)>>1; for (int l=1,r=n-1,mid;l<=r;){ // printf("fuckpp%d\n",(l+r)>>1); mid=((l+r)>>1); if (can(1,0,mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%d %d\n",res,ans); } return 0; }