BZOJ2067: [Poi2004]SZN【贪心DP】

题目描述:

n个点的树,问最少需要多少条线才能覆盖所有边,以及最少用线的情况下最长线的最短长度。
一条边只能被覆盖一次,线与线之间点可以相交。
n<=10000

题目分析:

我们先不考虑最少用线。
一个点 i i i(非根节点),由于它的父亲要被覆盖,所以最少用线的情况下这条线一定可以往其中一个儿子延伸,其余儿子连到自己的这条线就要两两配对,用 d [ i ] d[i] d[i]表示 i i i的度数,则 i i i的贡献就是 d [ i ] − 1 2 \frac {d[i]-1}2 2d[i]1(不计连向上面的边),根节点没有向上的边,所以要加一。

通过上面这个贪心就可以大致发现线的形成方式是固定的,即 i i i子树中用多少条线的数量已经固定了,那么就只需要考虑向上连的这条线的长度了。
f [ i ] f[i] f[i]表示 i i i连向父亲的这条线的最短长度,选一条边上传,剩下的配对,这时候就要考虑怎么使最长线最短了。
这应该是一个经典的二分问题,二分一个 M i d Mid Mid表示最长长度,向上传的时候将儿子排序,二分能够向上传的最短长度,然后检验剩下的线配对能否<=Mid。

实现中有几个细节需要注意:

  • 根节点不能向上传,直接检验。
  • 如果 i i i的儿子为偶数个,那么可以不向上传而直接让儿子配对,提前检验。
  • 如果要上传,儿子为偶数个,那么必须留下最大的一个儿子单独成线不配对(这样和上面一种情况用线数相同),剩下的部分同奇数,二分上传哪一个,然后剩下的配对。
  • 配对的检验可以直接最大的和最小的依次配对,正确性比较显然。
  • 也可以不二分上传哪一个,可以用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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值