【BZOJ2067】SZN(二分,动态规划,贪心)
题面
Description
String-Toys joint-stock 公司需要你帮他们解决一个问题. 他们想制造一个没有环的连通图模型. 每个图都是由一些顶点和特定数量的边构成. 每个顶点都可以连向许多的其他顶点.一个图是连通且无环的. 图是由许多的线做成的.一条线是一条连接图中两个顶点之间的路径.由于一些技术原因,两条线之间不能有重叠的部分,要保证图中任意一条边都被且仅被一条线所覆盖.由于一些技术原因,做一个这样的图的模型的费用取决于用了多少条线以及最长的那条的长度. (每条边的长度都为1.),给出对应的图,求出最少能用多少条线以及在用最少线的情况下最长的那根线最短可以为多少.
Input
第一行仅包含一个数n – 顶点的总数, 2 <= n <= 10 000. 顶点从1 到 n进行编号. 接下来的n - 1 行描述这些边, 每行两个数a 和 b, 1 <= a, b <= n, a <> b. 表示顶点a和顶点b之间有一条边.
Output
输出两个数,最少用多少条线以及在用最少线的情况下最长线最短可以为多少.
Sample Input
9
7 8
4 5
5 6
1 2
3 2
9 8
2 5
5 8
Sample Output
4 2
题解
首先第一问答案是\(1+\sum (d_i-1)/2\),其中\(d_i\)是\(i\)的度数。这个东西你可以认为是每个节点的所有儿子两两配对,而多出来的部分则可以延伸到父亲上面去继续做。
那么只需要考虑第二问。我们二分一个答案,设\(f[i]\)表示可以向上延伸的最小长度,那么每次对于一个点,把它的所有儿子拿出来排个序,看看延伸上去的最少长度是多少。
当然,这里要分奇偶性来看。如果一个点的儿子数是奇数,那么我们排序之后二分最小的那个延伸上去的儿子。如果是偶数,我们先尝试两两配对,如果不合法那么再考虑一下前面的式子,允许有一个儿子可以延伸到父亲去,而偶数的贡献则只需要匹配儿子数/2-1对,所以可以直接把最大值去掉再当成奇数尝试匹配。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 10010
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1,dg[MAX];
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,ans=1,f[MAX],mid;
int S[MAX],top,vis[MAX];bool fl;
void dfs(int u,int ff)
{
if(!fl)return;
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=ff)dfs(e[i].v,u);
top=f[u]=0;
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=ff)S[++top]=f[e[i].v]+1;
sort(&S[1],&S[top+1]);
if(!top)return;
if(S[top]>mid){fl=false;return;}
if(top%2==0)
{
for(int i=1;i<=top/2;++i)
if(S[i]+S[top-i+1]>mid)
{
if(u!=1){--top;break;}
fl=false;return;
}
}
if(top%2==1)
{
int l=1,r=top,ret=top+1;
while(l<=r)
{
int Mid=(l+r)>>1;bool chk=true;
for(int i=1,j=top;;++i,--j)
{
if(i==Mid)++i;if(j==Mid)--j;
if(i>=j)break;
if(S[i]+S[j]>mid){chk=false;break;}
}
if(chk)ret=Mid,r=Mid-1;
else l=Mid+1;
}
if(ret>top){fl=false;return;}
else f[u]=S[ret];
}
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
dg[u]++;dg[v]++;
}
for(int i=1;i<=n;++i)ans+=(dg[i]-1)/2;
int l=1,r=n,ret=1;
while(l<=r)
{
mid=(l+r)>>1;fl=true;
dfs(1,0);memset(vis,0,sizeof(vis));
if(fl)ret=mid,r=mid-1;
else l=mid+1;
}
printf("%d %d\n",ans,ret);
return 0;
}