20201023 day43 复习11:图论完全复习

1 并查集

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
const int maxn=5e4+5;
int fa[maxn],n;
void init(){
	for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int x){
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
void uni(int x,int y){
	fa[find(x)]=find(y);
}
int main(){
	int m;
	n=read(),m=read();
	init();
	while(m--){
		int a,b,c;
		a=read(),b=read(),c=read();
		if(a==1) uni(b,c);
		else{
			if(find(b)==find(c)) printf("Y\n");
			else printf("N\n");
		}
	}
	return 0;
}

P1525 关押罪犯

problem

一组人中两个人会有一个冲突值。把这组人分成两组,使得每组人之间的冲突值最小

solution

因为它们带有权值,因此排序是必须的,我们要尽可能让危害大的罪犯在两个监狱里。
那么,再结合敌人的敌人和自己在一个监狱的规律合并。
当查找时发现其中两个罪犯不可避免地碰撞到一起时,只能将其输出并结束。
还有一点很重要,就是没有冲突时一定输出0!!!

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
const int maxn=1e5+5;
int fa[maxn];
struct node{
	int x,y,z;
}f[maxn];
int n,m,a[maxn],b[maxn];
bool cmp(node x,node y){
	return x.z>y.z;
}
void init(){
	for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int x){
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
void uni(int x,int y){
	fa[find(x)]=find(y);
}
bool islian(int x,int y){
	if(find(x)==find(y)) return 1;
	return 0;
}
int main(){
	int m;
	n=read(),m=read();
	init();
	for(int i=1;i<=m;i++) f[i].x=read(),f[i].y=read(),f[i].z=read();
	sort(f+1,f+m+1,cmp);
	for(int i=1;i<=m+1;i++){
		if(islian(f[i].x,f[i].y)) {printf("%d",f[i].z);break;}
		else{
			if(!b[f[i].x]) b[f[i].x]=f[i].y;
			else uni(b[f[i].x],f[i].y);
			if(!b[f[i].y]) b[f[i].y]=f[i].x;
			else uni(b[f[i].y],f[i].x);
		}
	}
	return 0;
}

2 最小生成树

Kruskal

先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

证明:刚刚有提到:如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树。所以不难发现,当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
const int maxn=1e5+5;
const int maxm=2e5+10;
int n,m;
struct node{
	int nxt,val,to;
}edge[maxm];
int head[maxm],cnt,fa[maxm];
void init(){
	for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.val<y.val;
}
void add(int x,int y,int v){
	cnt++;
	edge[cnt].to=y;
	edge[cnt].val=v;
	edge[cnt].nxt=head[x];
	head[x]=cnt;
}
int tgn,tgto,ans;
int main(){
	memset(head,-1,sizeof(head));
	n=read(),m=read();
	init();
	for(int i=1;i<=m;i++) edge[i].nxt=read(),edge[i].to=read(),edge[i].val=read();
	sort(edge+1,edge+1+m,cmp);
	for(int i=1;i<=m;i++){
		tgn=find(edge[i].nxt);tgto=find(edge[i].to);
		if(tgn==tgto) continue;//已经联通
		ans+=edge[i].val;//边权记录答案
		fa[tgto]=tgn;//联通
		if(++cnt==n-1) break;//停止
	}
	if(ans!=0) printf("%d",ans);
	else printf("orz");
	return 0;
}

Prim

Prim和最短路中的dijkstra很像,由于速度问题,所以这里用链式前向星存图。Prim的思想是将任意节点作为根,再找出与之相邻的所有边(用一遍循环即可),再将新节点更新并以此节点作为根继续搜,维护一个数组:dis,作用为已用点到未用点的最短距离。

证明:Prim算法之所以是正确的,主要基于一个判断:对于任意一个顶点v,连接到该顶点的所有边中的一条最短边\((v,v_j)\)必然属于最小生成树(即任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include <iostream>
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
#define inf 123456789
const int maxn=5e3+5;
const int maxm=2e5+10;
int n,m;
struct node{
	int nxt,val,to;
}e[maxm<<1];
int head[maxm],cnt,dis[maxn],tot,now=1,ans;
bool vis[maxn];
void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].val=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
	return ;
}
int prim(){
	for(int i=2;i<=n;i++) dis[i]=inf;
	for(int i=head[1];i;i=e[i].nxt) dis[e[i].to]=min(dis[e[i].to],e[i].val);
	while(++tot<n){
		int minn=inf;
		vis[now]=1;
		//标记已经走过,枚举每一个没有使用的点,找出最小值作为新边,注意是枚举1~n
		for(int i=1;i<=n;i++){
			if(!vis[i]&&minn>dis[i]) minn=dis[i],now=i;
		}
		ans+=minn;
		//枚举now的所有连边,更新dis数组
		for(int i=head[now];i;i=e[i].nxt){
			int y=e[i].to;
			if(dis[y]>e[i].val&&!vis[y]) dis[y]=e[i].val;
		}
	}
	return ans;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int a,b,c;
		a=read(),b=read(),c=read();
		add(a,b,c);add(b,a,c);
	}
	printf("%d",prim());
	return 0;
}

3 单源最短路

dijkstra+堆优化

每次可以调用堆顶,不用\(O(n)\)扫一遍寻找最短路

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include <iostream>
#include <queue>
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
#define inf 2147483647
const int maxn=1e5+5;
const int maxm=1e6+5;
struct node{
	int nxt,val,to;
}edge[maxm<<1];
int head[maxm],cnt;
int n,m,x,y,z,tot=0,s;
int dis[maxn],v[maxm];
priority_queue <pair<int,int> > q;
void add(int u,int v,int w){
	edge[++cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}
int main(){
	n=read(),m=read(),s=read();
	for(int i=1;i<=m;i++) x=read(),y=read(),z=read(),add(x,y,z);
	for(int i=1;i<=n;i++) dis[i]=inf,v[i]=0;
	dis[s]=0;
	q.push(make_pair(0,s));//起点最短路为0,入队
	while(!q.empty()){
		int x=q.top().second;q.pop();
		if(v[x]) continue;
		v[x]=1;
		for(int i=head[x];i;i=edge[i].nxt){
			int y=edge[i].to;
			if(dis[y]>dis[x]+edge[i].val)
				dis[y]=dis[x]+edge[i].val,q.push(make_pair(-dis[y],y));
		}
	}
	for(int i=1;i<=n;i++) printf("%d ",dis[i]);
	return 0;
}

Floyd

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include <iostream>
#include <queue>
using namespace std;
long long read(){
	long long x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
#define inf 2147483647
const int maxk=7005;
long long a[maxk][maxk];
long long s,n,m,u,v,d;
int main(){
	n=read(),m=read(),s=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=inf;
	for(int i=1;i<=m;i++)	
		u=read(),v=read(),d=read(),a[u][v]=min(a[u][v],d);
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			if(i==k||a[i][k]==inf) continue;
			for(int j=1;j<=n;j++) a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
		}
	}
	a[s][s]=0;
	for(int i=1;i<=n;i++) printf("%lld ",a[s][i]);
	return 0;
}

最小环

Floyd判断最小环

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include <iostream>
#include <queue>
using namespace std;
long long read(){
	long long x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
#define inf 0x3f3f3f3f
const int maxn=105;
long long a[maxn][maxn],f[maxn][maxn];
long long n,m,u,v,w,ans=inf;
//n个点,m条线,ans记录最小环
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i!=j) a[i][j]=inf,f[i][j]=inf;//两个点不相同就赋值无限大,即为不相邻
	for(int i=1;i<=m;i++){
		u=read(),v=read(),w=read();
		a[u][v]=w;a[v][u]=w;
		f[u][v]=w;f[v][u]=w;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<k;i++)
			for(int j=i+1;j<k;j++)
				ans=min(ans,f[i][j]+a[i][k]+a[k][j]);//判断最小环
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]),f[j][i]=f[i][j];
		//更新最短路
	}
	if(ans==inf) printf("No solution.");
	else printf("%lld",ans);
	return 0;
}

最长路

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include <iostream>
#include <queue>
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x*=10,x+=ch^48,ch=getchar();}
	return x*f;
}
#define inf 100000000
const int maxn=5e4+5;
int dis[maxn],w[maxn];
int n,m,minn,f[maxn][3];
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++) dis[i]=w[i]=inf,f[i][1]=f[i][2]=0;
	for(int i=1;i<=m;i++){
		int a,b,c;
		a=read(),b=read(),c=read();
		f[i][1]=a,f[i][2]=b;w[i]=-c;
	}
	dis[1]=0;
	for(int i=1;i<=n-1;i++)
		for(int j=1;j<=m;j++)
			dis[f[j][2]]=min(dis[f[j][2]],dis[f[j][1]]+w[j]);
	if(dis[n]!=0) printf("%d",-dis[n]);
	else printf("-1");
	return 0;
}

SPFA

4 树的深度、LCA、树的直径、树的重心、树上差分

树的直径

#include<cstdio>
#include<algorithm>
#include<cstring>
#include <cmath>
using namespace std;
long long read(){
	long long a=0,op=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;
}
void bfs(int s){
	int i,j,now;
	queue<int> q;
	memset(v,0,sizeof(v));
	memset(dis,0,sizeof(dis));
	q.push(s);v[s]=1;
	while(!q.empty){
		now=q.front();q.pop();
		for(i=last[now];i;i=e[i].next){
			int h=e[i].to;
			if(!v[h]){
				v[h]=true;
				dis[h]=dis[now]+e[i].v;
				q.push(h);
			}
		}
	}
	for(i=1,ans=0;i<=n;i++){
		if(dis[i]>ans) ans=dis[i],point=i;
	}
}
int main(){
	bfs(s);bfs(point);
}

5 拓扑排序、查分约束

6 点双、边双、强连通、割点

7 dfs序、二分图

8 树链剖分

9 虚树

10 欧拉图、欧拉回路、哈密顿图

11 树的启发式合并

posted @ 2020-10-23 07:53  刘子闻  阅读(108)  评论(0编辑  收藏  举报