• [NOIP2018]赛道修建(树形dp+二分)


    弱鸡萌新2018年难忘的骗分之旅

    花了2个小时骗分2333

    题意

    从n个点构成的的树中取出m条边不重复路径,使得最小的路径最长

    思路

    1. 由于没有一个确定的限制且问题具有单调性,首先肯定想到二分答案,设该数为x,那么需要找出长度大于等于x的路径条数

    2. 对于一个子树i,考虑它的贡献,分为两种:① i子树中选一条或两条链,使它们拼起来大于等于x ,② 父亲节点通过连向i子树的这条边使用i子树中的一条链,这个贡献最多只能有一次(因为父亲到i的边只有一条)。那么把哪一条链贡献给父亲呢?使用贪心策略

    3. 贪心:让i子树自己内部能配对的尽量配对,达到最多的配对数之后,将未配对的最大链贡献给父亲即可。这样的正确性是显然的,因为如果i子树中有两条链本来可以配对却不让它们配对,那么路径数少1,把其中一条贡献给父亲,通过2中叙述可知,这样最多使路径数加1,但是会多浪费一些父亲子树中的一些边,肯定不如直接配对。

    4. 在使配对数最多的情况下,由于还要把未配对最大链贡献给父亲,所以这时候可以二分最大的链,检验如果没有用这条链,是否还能达到最大匹配数,如果能则说明可以不用这条链,即可以贡献给父亲

    实现

    上述思路总结起来的实现就是: 二分+排序+二分

    排序用vector或者multiset即可

    Code:

    #include<bits/stdc++.h>
    #define N 50005
    using namespace std;
    const int INF = 2000000000;
    int n,m,limit,sum;
    vector <int> e[N];
    
    struct Edge
    {
    	int next,to,dis;
    }edge[N<<1];int head[N],cnt=1;
    void add_edge(int from,int to,int dis)
    {
    	edge[++cnt].next=head[from];
    	edge[cnt].to=to;
    	edge[cnt].dis=dis;
    	head[from]=cnt;
    }
    template <class T>
    void read(T &x)
    {
    	char c;int sign=1;
    	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
    }
    bool ck(int rt,int pos,int l,int r,int goal)//不用pos是否能凑出goal个 
    {
    	int tot=0;
    	while(l<r)
    	{
    		if(r==pos) --r;
    		if(l==pos) ++l;
    		while((e[rt][l]+e[rt][r]<limit||l==pos)&&(l<r)) ++l;
    		if(!(l<r)) break;
    		++tot;++l;--r;
    	}
    	return (tot>=goal);
    }
    int dfs(int rt,int fa)
    {
    	for(int i=head[rt];i;i=edge[i].next)
    	{
    		int v=edge[i].to;
    		if(v==fa) continue;
    		e[rt].push_back(edge[i].dis+dfs(v,rt));
    	}
    	if(e[rt].empty()) return 0; 
    	sort(e[rt].begin(),e[rt].end());
    	int l=0,r=(int)e[rt].size()-1;
    	while(e[rt][r]>=limit) ++sum,--r;//单独成链 
    	int rest=r+1,tot=0;
    	while(l<r)
    	{
    		while(e[rt][l]+e[rt][r]<limit&&(l<r)) ++l;
    		if(!(l<r)) break;
    		++tot;++l;--r;
    	}
    	sum+=tot;
    	if(tot*2==rest) return 0;//完全配对 
    	//否则一定剩下至少一个 
    	l=0;r=rest-1;int ret=0;
    	while(l<=r)//二分出返回值 
    	{
    		int mid=(l+r)>>1;
    		if(ck(rt,mid,0,rest-1,tot)) l=mid+1,ret=max(mid,ret);
    		else r=mid-1;
    	}
    	return e[rt][ret];
    }
    bool check(int x)//求所有大于等于x长度的赛道 
    {
    	for(int i=1;i<=n;++i) e[i].clear();
    	limit=x;sum=0;
    	dfs(1,0);
    	return (sum>=m);
    }
    int main()
    {
    	read(n);read(m);
    	for(int i=1;i<n;++i)
    	{
    		int x,y,z;
    		read(x);read(y);read(z);
    		add_edge(x,y,z);
    		add_edge(y,x,z);
    	}
    	int l=0,r=INF,ans=-INF;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;
    		if(check(mid)) l=mid+1,ans=max(ans,mid);
    		else r=mid-1;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    vim tab转换成space
    vs计算项目代码总行数
    读取xml文件
    复选框全选、取消全选
    网页上用js禁用鼠标右键
    腾讯、微软、亚马逊测试站点
    限制文本域输入字符个数,即使显示输入字符数和剩余字符数,超出不显示!
    DataUtil
    DES加密解密类
    JsonHelper(Json帮助类)
  • 原文地址:https://www.cnblogs.com/Chtholly/p/11234691.html
Copyright © 2020-2023  润新知