版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目描述:
n个点的树,问最少需要多少条线才能覆盖所有边,以及最少用线的情况下最长线的最短长度。
一条边只能被覆盖一次,线与线之间点可以相交。
n<=10000
题目分析:
我们先不考虑最少用线。
一个点
(非根节点),由于它的父亲要被覆盖,所以最少用线的情况下这条线一定可以往其中一个儿子延伸,其余儿子连到自己的这条线就要两两配对,用
表示
的度数,则
的贡献就是
(不计连向上面的边),根节点没有向上的边,所以要加一。
通过上面这个贪心就可以大致发现线的形成方式是固定的,即
子树中用多少条线的数量已经固定了,那么就只需要考虑向上连的这条线的长度了。
设
表示
连向父亲的这条线的最短长度,选一条边上传,剩下的配对,这时候就要考虑怎么使最长线最短了。
这应该是一个经典的二分问题,二分一个
表示最长长度,向上传的时候将儿子排序,二分能够向上传的最短长度,然后检验剩下的线配对能否<=Mid。
实现中有几个细节需要注意:
- 根节点不能向上传,直接检验。
- 如果 的儿子为偶数个,那么可以不向上传而直接让儿子配对,提前检验。
- 如果要上传,儿子为偶数个,那么必须留下最大的一个儿子单独成线不配对(这样和上面一种情况用线数相同),剩下的部分同奇数,二分上传哪一个,然后剩下的配对。
- 配对的检验可以直接最大的和最小的依次配对,正确性比较显然。
- 也可以不二分上传哪一个,可以用set,将最大的和能够配对的尽量大的配对(upper_bound-1),然后删除,直到最后只剩一个就是上传的最小的长度。
Code:
#include<bits/stdc++.h>
#define maxn 10005
using namespace std;
int n,deg[maxn],f[maxn],g[maxn],cnt,Mid;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
bool check(int x){
for(int l=1,r=cnt;l<r;l++,r--){
l==x&&l++,r==x&&r--;
if(l<r&&g[l]+g[r]>Mid) return 0;
}
return 1;
}
bool dfs(int u,int ff){
for(int i=fir[u];i;i=nxt[i]) if(to[i]!=ff&&!dfs(to[i],u)) return 0;
cnt=0;
for(int i=fir[u];i;i=nxt[i]) if(to[i]!=ff) g[++cnt]=f[to[i]];
sort(g+1,g+1+cnt);
if(u==1) return check(0);
if(!(cnt&1)){
if(check(0)) {f[u]=1;return 1;}
cnt--;
}
int l=1,r=cnt+1,mid;
while(l<r){
mid=(l+r)>>1;
if(check(mid)) r=mid;//if(g[mid]<Mid&&check(mid)) is wrong.
else l=mid+1;
}
if(l==cnt+1) return 0;
else return (f[u]=g[l]+1)<=Mid?1:0;
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x),deg[x]++,deg[y]++;
int sum=1;
for(int i=1;i<=n;i++) sum+=(deg[i]-1)>>1;
int l=1,r=n-1;
while(l<r){
Mid=(l+r)>>1;
if(!dfs(1,0)) l=Mid+1;
else r=Mid;
}
printf("%d %d\n",sum,l);
}