NOIP2018 Day1T3 赛道修建(二分 贪心)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/C20180602_csq/article/details/102637607

题目链接:https://www.luogu.org/problem/P5021

题解

题目要求最短链最长,我们可以直接二分最短链长度

考虑如何验证

对于一个二分的最短链长度lim,我们要让树中剖分的链长度>=lim,并且剖分出的链数尽量多

考虑贪心来验证

每一个节点设一个权值 f ,表示它在满足条件(就是上面的条件)时,能够提供给父亲的最大链长

对于一个点,把它所有的儿子都按g=(权值 f +边长 cd )排序

由于链数要尽量多,我们可以直接把g>=lim的剖分出来,ans++

剩下的两两组合到一起:

先按g从小到大枚举一个儿子i(没有被匹配),二分另外一个儿子j,让他们的权值g加起来刚好等于lim,或者略大于lim

如果另外一个儿子j已经匹配了,就依次往后找(感觉最坏O(n^2),ep:1 1 ... 1 3 4 5 ... 25000 lim=4),直到找到一个没有匹配的

如果当前的儿子i无法找到能够他匹配的点,就可以把它提供给父亲了(当然要提供那个最大的无法匹配的儿子)

然后就是各种细节处理了

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
int n,m;
#define N 50005
int fir[N],to[2*N],nxt[2*N],cd[2*N],d[N],cnt;
void adde(int a,int b,int c)
{
	to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;cd[cnt]=c;
	to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;cd[cnt]=c;
	d[a]++;d[b]++;
}
int lim,f[N],g[N],t,ans;
bool vis[N];
void dfs(int u,int fa)
{
	int v,p;
	for(p=fir[u];p;p=nxt[p]){
		v=to[p];
		if(v!=fa)
			dfs(v,u);
	}
	t=0;
	for(p=fir[u];p;p=nxt[p]){
		v=to[p];
		if(v!=fa){
			g[++t]=f[v]+cd[p];
			vis[t]=0;
		}
	}
	sort(g+1,g+t+1);
	while(g[t]>=lim){ans++;t--;}
	int i,j;
	for(i=1;i<=t;i++){
		if(!vis[i]){
			for(j=lower_bound(g+i+1,g+t+1,lim-g[i])-g;j<=t;j++)if(!vis[j])break;
			if(j>t){f[u]=g[i];continue;}
			vis[i]=1;vis[j]=1;
			ans++;
		}
	}
}
int main()
{
	int i,u,v,w,l,r,mid;
	n=gi();m=gi();
	for(i=1;i<n;i++){
		u=gi();v=gi();w=gi();
		adde(u,v,w);
	}
	l=0;r=1000000000;
	while(l<r){
		memset(f,0,sizeof(f));
		lim=mid=(l+r)>>1;ans=0;dfs(1,0);
		if(ans>=m) l=mid+1;
		else r=mid;
	}
	printf("%d",l-1);
}

猜你喜欢

转载自blog.csdn.net/C20180602_csq/article/details/102637607