题目大意:http://www.lydsy.com/JudgeOnline/problem.php?id=2067
这个题一共两问,先求第一问:也就是问这个图最少需要几笔画出来, ans1=(奇点个数+1)/2
然后第二问就是一个树形DP,f[i]表示以i的子树下面最少需要补一个多长的链,如何转移f[i]呢?
先二分答案,设答案为Lim
把所有f[son[i]]扔到一个set里,然后在set里从后往前扫,把每一个最大的元素通过--upper_bound来找到和他配对里最大的,如果找不到配对就自己单成一条链,然后把这样单独成链里的长度取个最小值,f[i]=最小值+1。
如果没有任何一个需要单独成链的话,f[i]=1
然后注意如果f[i]>Lim,就需要给f[i]-Lim,链的总数++
最后链的总数如果=ans1,那么继续向下二分,否则向上二分
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int N=10005;
const int M=20005;
const int inf=1e9;
int n,cnt,to[M],nxt[M],lj[N],du[N],ans1,ans2,f[N],fa[N],t[N],t1[N],sum;
void ins(int f,int t) {cnt++,to[cnt]=t,nxt[cnt]=lj[f],lj[f]=cnt,du[f]++;}
void add(int f,int t) {ins(f,t),ins(t,f);}
void dfs(int x,int lim)
{
multiset<int>S;
f[x]=inf;
for(int i=lj[x];i;i=nxt[i])
if(to[i]!=fa[x])
{
fa[to[i]]=x;
dfs(to[i],lim);
S.insert(f[to[i]]);
}
multiset<int>::iterator it=S.end(),itt;
if(it!=S.begin())
{
it--;
while(1)
{
int tmp=*it;
S.erase(it);
sum++;
itt=S.upper_bound(lim-tmp);
if(itt!=S.begin())
{
itt--;
S.erase(itt);
}
else f[x]=min(f[x],tmp);
if(S.size()==0) break;
else it=--S.end();
}
}
if(f[x]==inf) f[x]=0;
else sum--;
f[x]++;
if(f[x]>lim) f[x]-=lim,sum++;
}
bool Judge(int x)
{
memset(f,0,sizeof(f));
sum=0;
dfs(1,x);
if(f[1]>1) sum++;
return sum==ans1;
}
int main()
{
scanf("%d",&n);
int x,y;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i=1;i<=n;i++) ans1+=(du[i]%2);
ans1=(ans1+1)/2;
printf("%d ",ans1);
int l=1,r=n-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(Judge(mid)) ans2=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans2);
}