树直径,并查集,超级源点,瓶颈树

树直径

各点最远距离

  • 问题
    实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度

  • 输入输出
    输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
    对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N)。

解题

  • 直径两端点
    直径概念——树中最长的路径。端点——直径两端点。
    一个点能到达的最远距离=到两端点最大距离。(下面将会证明)

  • 第一个端点
    从任意一点开始,能到达对的最远点一定是其中一个端点。(下面证明)

  • 另一个端点
    从第一个端点开始,能到达的最大点一定是另一个端点。(下面证明)

  • 三次遍历
    第一次,从任意一点开始,找出第一个端点。
    第二次,从第一个端点开始,找到第二个,并且记录各个点到达第一端点的距离。
    第三次,从第二端点开始,计算到达第二端点的距离(如果大于上次记录,修改)。

  • 寻找最距离
    从点P出发,最距离=max(周围点最远距离)。使用DFS遍历返回周围点的最远距离,记录最大。另外我们要知道当前遍历(后者说路径),从哪一点来的,因为是无向图,不能从当前点回到出发点(重复遍历死循环)。

  • 边界
    已经避免回到原点的情况,如果当前点不能再向其他点移动,即为结束。

  • 最远点
    维护从出发点到此节点最大距离,记录到最大距离的最远点。

证明

在这里插入图片描述
在这里插入图片描述

  • 端点
    从任意一点P开始。
    如果P找到的最远点是Q,Q有没有可能不是端点。假设直径两端点是AB。
    当AB与PQ相交,首先我们知道Q是P能到的最大点。如果AB是直径,那么AB>PQ,PQ>PC+CA(P到A小于到Q),得到CQ>CA。这显然不可能,因为AB是直径,AB>BC+CQ。同时也证明,任意一点P最远点一定在AB之中。
    如果AB与PQ不想交,AB>PQ,PN+NQ>PN+NM+MA,得QN+NM>AM+MN,QNB>AMB。不成立。所以P最远点在端点AB中。

代码

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=1e4+5;

struct edge{
	int p,w;
	edge(int a,int b){
		p=a,w=b;
	}
};
vector<edge> pnt[N];
int n,s;
int path[N],maxl;
void DFS(int u,int v,int l){
	int next,w;
	if(l>maxl){
		s=u;
		maxl=l;
	}
	for(int i=0;i<pnt[u].size();i++){
		next=pnt[u][i].p;
		w=pnt[u][i].w;
		if(next==v)continue;
		DFS(next,u,w+l);
		path[next]=max(path[next],w+l);
	} 
}


int main(){
	int a,b;
	while(cin>>n){
	maxl=0;
	for(int i=1;i<=n;i++){
		pnt[i].clear();
	}
	for(int i=2;i<=n;i++){
		cin>>a>>b;
        pnt[i].push_back(edge(a,b));
        pnt[a].push_back(edge(i,b));
	}
	memset(path,0,sizeof(path));
	DFS(1,0,0);
	DFS(s,0,0);
	DFS(s,0,0);
	for(int i=1;i<=n;i++){
		cout<<path[i]<<endl;
	}
	}
	return 0;
}

并查集

戴口罩

  • 问题
    新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
    如果一个感染者走入一个群体,那么这个群体需要被隔离!
    小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
    危!!!时间紧迫!!!!和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
  • 输入输出
    多组数据,对于每组测试数据:
    第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
    学生编号为0~n-1
    小A编号为0随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
    输出要隔离的人数,每组数据的答案输出占一行。

解题

  • 并查集
    要记录团体成员可以使用并查集,为了防止并查集内部过于混乱没有主次,定义一个数组记录每个并查集的大小(成员越多越大)两个并查集合并采用大树上挂小树。

  • 多个团体
    这道题要找的是与小A有直接或间接关联的所有人。
    直接关联就是与小A同一个团体的同学(这里称直接学生),间接关联就是与直接学生有关联的同学。
    因此,我们不用在意有几个团体,用一个并查集相连,最后小A的大团体就是最终人数。

代码

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;

const int N=3e4,M=5e2;
int uF[N];
int rank[N];
int find(int x){
	if(uF[x]==x)
		return x;
	return uF[x]=find(uF[x]);
}
bool unite(int a,int b){
	int aa,bb;
	aa=find(a),bb=find(b);
	if(aa==bb)
		return false;
	if(rank[a]>rank[b])
		uF[bb]=aa,rank[aa]+=rank[bb];
	else
		uF[aa]=bb,rank[bb]+=rank[aa];
	return true;
}

int main(){
	int n,m,a,b,t;
	cin>>n>>m;
	while(n||m){
	for(int i=0;i<n;i++)
		rank[i]=1;
	for(int i=0;i<n;i++)
		uF[i]=i;
	for(int i=0;i<m;i++){
		cin>>t>>a;
		for(int j=1;j<t;j++){
			cin>>b;
			unite(a,b);
		}
	}
	cout<<rank[find(0)]<<endl;
	cin>>n>>m;
	}
}

超级源点+最小生成树

农田灌溉

  • 问题
    东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵
    众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
    黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
    建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
    东东为所有的田灌氵的最小消耗
  • 输入输出
    第1行:一个数n ,第2行到第n+1行:数wi,第n+2行到第2n+1行:矩阵即pij矩阵
    输出东东最小消耗的MP值。

解题

  • kruskal
    这样的题很容易想到kruskal算法,生成最小树。

  • 超级源点
    生成最小树很简单,但是我们n个农田至少要有一个从天灌溉(作为水的源头),加入超级源点作为点0,生成一个0到n的最小树,W就是0到其它点的代价(权)。

  • 代码简化
    可以看到1到n之间的权是一个矩阵(设行列为i和j),作为一个无向图重复输入Pij和Pji是没有意义的。所以加入一个变量p,对于i<j的Pij直接输入到p(没有任何作用)。

代码

#include<iostream>
#include<algorithm>

using namespace std;

const int N=3e2+5;
int pnt[N],r[N];
int n,k=0;

struct edge{
	int a,b,w;
}e[N*N/2];

bool cmp(edge u,edge v){
	return u.w<v.w;
}

int find(int x){
	if(pnt[x]==x){
		return x;
	}
	return pnt[x] = find(pnt[x]);
}
bool unite(int a,int b){
	int aa,bb;
	aa = find(a), bb = find(b);
	if(aa == bb)
		return false;
	if(r[aa] > r[bb])
		pnt[bb] = aa, r[aa] += r[bb];
	else
		pnt[aa] = bb, r[bb] += r[aa];
	return true;
}
int main(){
	int v,sum=0;
	edge ne;
	cin>>n;
	for(int i = 0;i <= n;i++){
		pnt[i]=i;
		r[i]=1;
	}
	for(int i = 1;i <= n;i++){
		cin>>e[k].w;
		e[k].a=0;
		e[k++].b=i;
	}
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= n;j++){
			if(j>i) {
				cin>>e[k].w;
				e[k].a=i;
				e[k++].b=j;
			}
			else	
				cin>>v;
		}
	} 
	sort(e,e+k,cmp);
	for(int i = 0;i < k;i++){
		ne = e[i];
		if(unite(ne.a,ne.b)){
			sum+=ne.w;
		}
	}
	cout<<sum<<endl;
}

最小瓶颈生成树

数据中心

在这里插入图片描述

解题

  • 瓶颈生成树
    本题要求生成一棵树,这棵树的Tmax最小。
    Tmax=每个深度最大的Th,而每个Th=各自深度中最大的T,T就是点点之间的权。
    总结下来,生成一棵树使这个数的最大权能达到最小,即为最小瓶颈生成树。

  • 证明
    在无向图中,瓶颈生成树=最小生成树。
    下面用反证法来证明一下,假设最小生成树不是瓶颈生成树,也就是说存在一个树它的最大权比最小生成树权更小。
    我们最小生成树是从最小开始生成, 如果真的存在这样一颗树不是最小生成树的瓶颈生成树,生成树算法中一定会选定这个瓶颈树,假设不成立。

  • 补充
    无向图中,最小生成树一定是瓶颈树,最小生成树不一定是瓶颈生成树。
    例如:一个树有五个点,我们选中了四个边做最想生成树 1 3 2 2,它也是瓶颈生成树。但是,1 3 3 3也是瓶颈生成树。
    所以,一个树可能有多个瓶颈生成树。

代码

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 5e4+5,M=1e5+5;
int pnt[N],rnk[N];

struct edge{
	int u,v,w;
	bool operator<(const edge &a){
		return w<a.w; 
	}
}e[M];

int find(int x){
	if(x == pnt[x])
		return x;
	return pnt[x] = find(pnt[x]);
}
bool unite(int a,int b){
	int aa,bb;
	aa = find(a), bb = find(b);
	if(aa == bb)
		return false;
	if(rnk[aa]>rnk[bb]){
		pnt[bb] = aa, rnk[aa] += rnk[bb]; 
	}
	else{
		pnt[aa] = bb, rnk[bb] += rnk[aa];
	}
	return true;
}
int main(){
	int n,m,p;
	cin>>n>>m>>p;
	for(int i=0;i<m;i++){
		cin>>e[i].u>>e[i].v>>e[i].w;
	}
	for(int i=0;i<=n;i++){
		pnt[i]=i;
		rnk[i]=1;
	} 
	sort(e,e+m);
	p=0;
	int count=n-1;
	for(int i=0;i<m;i++){
		if(unite(e[i].u,e[i].v)){
			p=max(p,e[i].w);
			count--;
			if(!count)
				break;
		}
	}
	cout<<p<<endl;
}
发布了7 篇原创文章 · 获赞 2 · 访问量 199

猜你喜欢

转载自blog.csdn.net/Schrodingerofcat/article/details/105157934
今日推荐