zoukankan      html  css  js  c++  java
  • [Noip2018]赛道修建

    [Noip2018]赛道修建

    一.前言

    ​ 这题的部分分还是给的非常友好!STL真好用!题目链接

    二.20pts

    ​ 这是 (m=1) 的做法,只需要两遍dfs算树的直径就可以了,代码稍有注释。

    int est,sume;
    void dfs1(int x,int fa,int sumd){
    	if(sumd>sume)est=x,sume=sumd;//更新离他最远的点
    	for(int i=head[x];i;i=ne[i]){
    		if(to[i]==fa)continue;
    		dfs1(to[i],x,sumd+dis[i]);
    	}
    }
    
    if(m==1){
    		dfs1(1,0,0);
    		sume=0;
    		dfs1(est,0,0);//用离1最远的est再来一次
    		printf("%d",sume); 
    }
    

    三.40pts

    ​ 这是20分加上链的情况……

    ​ 给出一种错误思路:直接总和除m(一个特别大就可以hack)

    ​ 下面是正确的思路,二分一个最短长度埋伏他一手,然后贪心看最多能够凑出几个赛道来。

    bool check(int x){
    	int cnt=0,sum=0;
    	m_for(i,1,n-1){
    		if(sum+a[i]>=x)sum=0,cnt++;//又凑出一段
    		else sum+=a[i];
    	}
    	return cnt>=m;
    }
    //链式向前星存储的,special1是判断是不是链
    else if(!spe1){
    	m_for(i,1,n-1)
    	sume+=(a[i]=(dis[head[i]]!=a[i-1]?dis[head[i]]:dis[ne[head[i]]]));
        //这里偷个懒把长度总和和求出到i+1的长度合在一起写了
    	int l=1,r=sume,ans=1;//二分上界为长度总和
    	while(l<=r){
    		int mid=(l+r)>>1;
    		if(check(mid))l=mid+1,ans=mid;
    		else r=mid-1;
    	}
    	printf("%d",ans);
    }
    

    三.55pts

    就是 (a_i=1) 的菊花图,也是部分分的上限。对于菊花图,我们要么是连着1的两条边凑一起,要么一条边自成一家。这里先 sort 从大到小排个序,然后取前 (2m) 个来拼赛道,按照最大配最小的来。这样可以使得赛道的最短长度最大((2m>n) 时相当于接上空气赛道,长度为0,也就是自成一家,不影响

    else if(!spe2){
    	for(int i=head[1];i;i=ne[i])a[to[i]]=dis[i];
    	sort(a+1,a+n+1,cmp);
    	int ans=1<<29;
    	for(int i=1;i<=m;++i)ans=min(ans,a[i]+a[(m<<1)-i+1]);
    	printf("%d",ans);
    }
    

    四.100pts

    ​ 本片题解的重头戏,满分思路来啦!

    ​ 首先还是二分长度埋伏他一手,凑出尽可能多的赛道。题目给出的大背景是一个树,我才用深搜,很显然的,遍历到一个点,我先让儿子们只凭借他们所在的子树凑出尽可能多的赛道来。必然的,儿子不可能将他们身上所有的边都用完,此时出现了若干条还没用的边,如图

    现在在红点,让蓝点完成了任务,蓝点反馈说我还有三条黑边没有用上,请求父亲帮助!也许这三条黑边任意一条加上红边就可以凑出赛道,又或许加上红边还不行,还要和蓝边的兄弟再操作一手才行……

    ​ 但是不管怎么样,这三条黑边都必须加上红边才可能有机会。并且在一条黑边获得宠幸后其他的黑边就再也没有机会了。本着贪心的原则,我们追求尽量多的赛道,那么肯定是从这三条黑边当中选一条最长的出来和红边相接。于是儿子做完任务后还需要返回一个最长的没有用到的假赛道。

    ​ 接下来考虑如何算出只依靠自己的子树的赛道数。很显然,儿子们都反馈给我了一些最长的假赛道,我从中将假赛道接起来就行。还剩下的无用假赛道选一个最长的返回给父亲。如此,这题就做完了。

    int dfs(int x,int fa,int &alf,int mid){
    	int ans=0;
    	multiset<int> s;//保存假赛道
    	for(int i=head[x];i;i=ne[i]){
    		if(to[i]==fa)continue;
    		int k=0;//儿子的最长假赛道
    		ans+=dfs(to[i],x,k,mid);//让儿子先做任务
    		k+=dis[i];//和红边配起来
    		if(k>=mid)ans++;//最好直接成为赛道
    		else s.insert(k);//不行就放入假赛道集合中
    	}
    	while(!s.empty()){
    		int k=*s.begin();//取一个最小假赛道
    		s.erase(s.begin());//直接扔掉
    		multiset<int>::iterator it=s.lower_bound(mid-k);//能配起来的最小假赛道
    		if(it!=s.end()){ans++;s.erase(it);}//找得到就拼起来,然后删掉
    		else alf=max(alf,k);//记录最长无用假赛道
    	}
    	return ans;
    }
    else{
    	dfs1(1,0,0);
    	sume=0;
    	dfs1(est,0,0);
    	int r=sume,l=1,ans;//二分上界为树的直径
    	while(l<=r){
    		int mid=(l+r)>>1;
    		int alf=0;
    		if(dfs(1,0,alf,mid)>=m)l=mid+1,ans=mid;
    		else r=mid-1;
    	}
    	printf("%d",ans);
    }
    

    最后将各个模块拼起来就行qwq

  • 相关阅读:
    屏蔽打开文件时提示“您尝试打开的文件xxx.xls的格式与文件扩展名指定的格式不一致。打开文件前请验证文件没有损坏且来源可信。是否立即打开该文 件?”
    sql实现MD5加密
    微信小程序在苹果手机的New Date坑
    命令行同步互联网时钟
    excel怎么把单元格内某个字标红,其他字不变
    js 字符串排序 String.prototype.localeCompare
    vscode 使用键盘运行 npm命令
    vscode 使用键盘打开 git分支面板
    给img标签的src属性使用file文件对象
    DOM0级 与 DOM2级
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13451546.html
Copyright © 2011-2022 走看看