• P5021 赛道修建 题解


    CSDN同步

    原题链接

    简要题意:

    在一棵树上求 (m) 条不相交的路径的最小值的最大值。

    本题部分分很多,而且本人也交了 (27) 次,所以一定要仔细讲部分分!

    算法一

    对于 (b_i = a_i + 1) 的数据,你发现这是一条链。

    也就是说,对这部分数据,题目简化为:

    将一个数组分为不相交的若干区间,使得它们权值和的最小值最大。

    一看,最小值最大,就是二分答案。

    验证方法也很简单。

    假设你当前的和是 ( ext{sum}),当前决策的数为 (x),验证的和为 (ans).

    此时如果 (sum+x>ans),那么将前面一段与当前的决策作为一段区间,并开启新的区间。

    否则,不开启新的区间。

    时间复杂度: (O(n log n)).

    实际得分:(20pts).

    (但是,如果你对所有情况都按链做,似乎还可以都对一个点)

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+1;
    
    inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
    
    int n,m,a[N],f=0;
    vector<pair<int,int> >G[N];
    bool chain=0; //链的部分分 
    
    inline void dfs(int dep,int bs) {
    //	printf("%d %d
    ",dep,bs);
    	for(int i=0;i<G[dep].size();i++) {
    		int x=G[dep][i].first,y=G[dep][i].second;
    		if(x==bs) continue;
    		dfs(x,dep); a[dep]=y; 
    	}
    } //把权值做成数组取出
    
    inline bool check(int dep) {
    	int t=0,sum=0;
    	for(int i=1;i<n;i++) {
    		if(sum+a[i]>=dep) sum=0,t++; //新开一个区间
    		else sum+=a[i]; //区间继承节点
    	} 	return t>=m;	
    }
    
    inline void subtask1() { //链 
    	dfs(1,0); //for(int i=1;i<n;i++) printf("%d ",a[i]); putchar('
    ');
    	int l=1,r=1e9,ans;
    	while(l<=r) {
    		int mid=(l+r)>>1;
    		if(check(mid)) ans=mid,l=mid+1;
    		else r=mid-1; //二分答案
    	} printf("%d
    ",ans);
    }
    
    int main(){
    	n=read(),m=read();
    	for(int i=1;i<n;i++) {
    		int x=read(),y=read(),z=read();
    		G[x].push_back(make_pair(y,z));
    		G[y].push_back(make_pair(x,z));
    		if(y!=x+1) chain=1;
    	} //dfs(1,0);
    	if(!chain) subtask1();
    	return 0;
    }
    

    算法二

    你发现有 (m=1) 的部分分。

    此时就是 树的直径

    树的直径定义:

    一棵树上长度最长的一条简单路径。

    众所周知,树的直径只需要两次 ( exttt{dfs}).

    即从任意一个点走到离它最远的点 (x),再从 (x) 走到它最远的点 (y).

    那么 (y gets x) 的这条路径就是直径。定义略,读者可自行学习。

    敲模板即可。

    时间复杂度:(O(n)).

    实际得分:(40pts).

    算法三

    仍然注意到,(a_i=1) 的菊花图部分分。

    对于菊花图,其实就是贪心。

    你把所有边从大到小排序,然后肯定是选最大的 (m) 个。

    那么,怎么配对呢?你发现,每条链要么单独,要么和别人拼起来,两条链作为一条链。

    显然,能拼接的我们就拼。

    所以,应将第 (i) 大的和第 (i) 小的配对,这样尽量平均。

    这个贪心就不证明了吧。

    时间复杂度:(O(n + m)).

    实际得分:(55pts).

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+1;
    
    inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
    
    int n,m,a[N],dis[N];
    vector<pair<int,int> >G[N];
    bool chain=0; //链的部分分 20
    bool flower; //菊花图的部分分 15 
    
    inline void dfs_get(int dep,int bs) {
    	for(int i=0;i<G[dep].size();i++) {
    		int x=G[dep][i].first,y=G[dep][i].second;
    		if(x==bs) continue;
    		dfs_get(x,dep); a[dep]=y; 
    	}
    }
    
    inline bool check(int dep) {
    	int t=0,sum=0;
    	for(int i=1;i<n;i++) {
    		if(sum+a[i]>=dep) sum=0,t++;
    		else sum+=a[i];
    	} 	return t>=m;	
    }
    
    inline void subtask1() { //链 
    	dfs_get(1,0);
    	int l=1,r=1e9,ans;
    	while(l<=r) {
    		int mid=(l+r)>>1;
    		if(check(mid)) ans=mid,l=mid+1;
    		else r=mid-1;
    	} printf("%d
    ",ans);
    	exit(0);
    }
    
    inline void dfs(int dep,int bs) {
    	for(int i=0;i<G[dep].size();i++) {
    		int x=G[dep][i].first,y=G[dep][i].second;
    		if(x==bs) continue;
    		dis[x]=dis[dep]+y; dfs(x,dep); 
    	}
    }
    
    inline int get_far(int x) { //找到离 x 最远的点,求直径 
    	memset(dis,0,sizeof(dis));
    	dfs(x,0); int maxi=0,maxh;
    	for(int i=1;i<=n;i++) 
    		if(dis[i]>maxi) maxi=dis[i],maxh=i;
    	return maxh;	
    }
    
    inline void dfs_get_2() {
    	for(int i=0;i<G[1].size();i++) {
    		int x=G[1][i].first,y=G[1][i].second;
    		a[x-1]=y;
    	}
    }
    
    int main(){
    	n=read(),m=read();
    	for(int i=1;i<n;i++) {
    		int x=read(),y=read(),z=read();
    		G[x].push_back(make_pair(y,z));
    		G[y].push_back(make_pair(x,z));
    		if(y!=x+1) chain=1;
    		if(x!=1) flower=1;
    	} if(!chain) subtask1(); //链 
    	if(m==1) {
    		int t=get_far(1);
    		t=get_far(t);
    		printf("%d
    ",dis[t]); return 0;
    	} //直径 
    	if(!flower) {
    		dfs_get_2(); int ans=1e9;
    		sort(a+1,a+n); reverse(a+1,a+n);
    		for(int i=1;i<=m;i++) ans=min(ans,a[i]+a[(m<<1)-i+1]);
    		printf("%d
    ",ans);
    	} //菊花图 
    	return 0;
    }
    

    算法四

    其实呢,分支不超过 (3) 可以认为是正解的弱化版。

    因为正解的复杂度和分支数有一定关联。

    首先我们二分答案,下面需要用 (O(n))(O(n log n)) 的时间验证一个数 (mid)

    对于一条超过 (mid) 的链,直接扔掉,计入答案。

    否则,我们叫它 “半链”。

    一条半链,要么继承它的父节点,要么就和另一个半链拼在一起,要么就抛弃。

    那么,对于以 (i) 为根的子树,所有的半链要两两配对(也可以抛弃一部分)使得 (geq mid).

    这里的半链是所有的 (f_k (k in exttt{son(i)})),即所有的儿子节点的半链。

    这时候我们用到了排序,贪心,双指针。

    首先从小到大排序,然后用双指针贪心即可。

    对于剩下的半链,在剩下的半链中取最大的继承父节点,作为一个新的半链,等待上面的半链处理。

    这里我们需要一个数据结构,维护这些半链。

    显然,如果排序的话,时间复杂度将会达到 (O(n k log k)). 其中 (k) 为每个节点儿子个数的和,接近于 (n)

    所以会被卡到 (O(n^2 log n)).

    这样只能得到 (85pts).

    我们想到了 ( exttt{mutilset}).

    维护之后,复杂度从 (O(n^2 log n)) 降到了 (O(n log^2 n)).(最坏)

    实际得分:(100pts).

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+1;
    
    inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
    
    int n,m,a[N],dis[N];
    vector<pair<int,int> >G[N];
    bool chain=0; //链的部分分 20
    bool flower; //菊花图的部分分 15 
    int dist[N],sum=0;
    multiset<int>s; //正解 
    
    inline void dfs_get(int dep,int bs) {
    	for(int i=0;i<G[dep].size();i++) {
    		int x=G[dep][i].first,y=G[dep][i].second;
    		if(x==bs) continue;
    		dfs_get(x,dep); a[dep]=y; 
    	}
    }
    
    inline bool check(int dep) {
    	int t=0,sum=0;
    	for(int i=1;i<n;i++) {
    		if(sum+a[i]>=dep) sum=0,t++;
    		else sum+=a[i];
    	} 	return t>=m;	
    }
    
    inline void subtask1() { //链 
    	dfs_get(1,0);
    	int l=1,r=1e9,ans;
    	while(l<=r) {
    		int mid=(l+r)>>1;
    		if(check(mid)) ans=mid,l=mid+1;
    		else r=mid-1;
    	} printf("%d
    ",ans);
    	exit(0);
    }
    
    inline void dfs__(int dep,int bs) {
    	for(int i=0;i<G[dep].size();i++) {
    		int x=G[dep][i].first,y=G[dep][i].second;
    		if(x==bs) continue;
    		dis[x]=dis[dep]+y; dfs__(x,dep); 
    	}
    }
    
    inline int get_far(int x) { //找到离 x 最远的点,求直径 
    	memset(dis,0,sizeof(dis));
    	dfs__(x,0); int maxi=0,maxh;
    	for(int i=1;i<=n;i++) 
    		if(dis[i]>maxi) maxi=dis[i],maxh=i;
    	return maxh;	
    }
    
    inline void dfs_get_2() {
    	for(int i=0;i<G[1].size();i++) {
    		int x=G[1][i].first,y=G[1][i].second;
    		a[x-1]=y;
    	}
    }
    
    inline int dfs(int x,int fa,int mid) {
    	//二分答案中的检验
    	int tot=0;
    	for(int i=0;i<G[x].size();i++) {
    		int u=G[x][i].first,v=G[x][i].second;
    		if(u==fa) continue;
    		tot+=dfs(u,x,mid);
    		if(tot>=m) return tot;
    	} dist[x]=0; s.clear();
    	for(int i=0;i<G[x].size();i++) {
    		int u=G[x][i].first,v=G[x][i].second;
    		if(u==fa) continue;
    		if(dist[u]+v>=mid) tot++;
    		else s.insert(dist[u]+v);
    	} 
    	for(multiset<int>::iterator i=s.begin();i!=s.end() && s.size();) {
    		//双指针
    		 multiset<int>::iterator nxt=s.lower_bound(mid-(*i));
    		 if(nxt==i && nxt!=s.end()) nxt++;
    		 if(nxt==s.end()) {i++;continue;}
    		 s.erase(nxt),s.erase(i++); tot++;
    		 if(tot>=m) return tot;
    	} if(!s.empty()) dist[x]=*s.rbegin();
    	return tot;
    }
    
    int main(){
    	n=read(),m=read();
    	for(int i=1;i<n;i++) {
    		int x=read(),y=read(),z=read();
    		G[x].push_back(make_pair(y,z));
    		G[y].push_back(make_pair(x,z));
    		if(y!=x+1) chain=1;
    		if(x!=1) flower=1;
    	} if(!chain) subtask1(); //链 
    	if(m==1) {
    		int t=get_far(1);
    		t=get_far(t);
    		printf("%d
    ",dis[t]); return 0;
    	} //直径 
    	if(!flower) {
    		dfs_get_2(); int ans=1e9;
    		sort(a+1,a+n); reverse(a+1,a+n);
    		for(int i=1;i<=m;i++) ans=min(ans,a[i]+a[(m<<1)-i+1]);
    		printf("%d
    ",ans); return 0;
    	} //菊花图 
    	int l=1,r=5e9,ans;
    	while(l<=r) {
    		int mid=(l+r)>>1;
    		if(dfs(1,1,mid)>=m) l=mid+1,ans=mid;
    		else r=mid-1;
    	} printf("%d
    ",ans);
    	return 0;
    }
    
    
  • 相关阅读:
    MySQL Explain学习笔记
    postman测试文件上传接口教程
    URLDecoder异常Illegal hex characters in escape (%)
    第三章 jQuery总结 参考文本
    史上最简单的一道面试题!坑人吧
    cookie和session的关联关系
    服务器配置
    Rancher 添加主机无法显示、添加主机无效的解决办法
    .NET Core 跨平台 串口通讯 ,Windows/Linux 串口通讯,flyfire.CustomSerialPort 的使用
    .Net Core 跨平台应用使用串口、串口通信 ,可能出现的问题、更简洁的实现方法
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12580239.html
Copyright © 2020-2023  润新知