Note3

圖論

所有模板的鄰接表存圖都用的struct{…}e[M];

· 存圖

鄰接矩陣(略)、【vector、鄰接表/ 鏈式前向星】
n個點,m條邊

// 鏈式前向星/鄰接表
const int M=100007,N=10005;
struct EDGE{
	int v,w,nxt;
}e[M];
int head[N],cnt;//memset(head,-1,sizeof(-1)); 
void addedge(int x,int y,int z){
	e[cnt].v=y;
	e[cnt].w=z;
	e[cnt].nxt=head[x];
	head[x]=cnt++;
}
// 遍歷now點的所有連點
for(int i=head[now];i!=-1;i=e[i].nxt){
	...
}
// vector
const int N=10005;
struct node{int to,cost;}
vector<node>e[N];
inline void addedge(int x,int y,int z){
	node t(y,z);
	e[x].push_back(t);
}
//遍歷now點的連點
for(int i=0;i<e[now].size();i++){
	node t=e[now][i];
	...
}

· 並查集

//初始化for(int i=1;i<=n;i++)fa[i]=i;
//遞歸式
int fa[maxn]; 
int find(int u){
	if(fa[u]!=u)fa[u]=find(fa[u]);
	return fa[u];
}

//迭代式
int find(int u){ 
	int i=u,fu=u,j;
	while(fu!=fa[fu])fu=fa[fu];//查詢 
	while(i!=fu){//路徑壓縮 
		j=fa[i];//先記錄下i的父親 
		fa[i]=fu;//i直接跟祖先相連 
		i=j;	//接下來修改i的父親節點 
	}
	return fu;
}

//合併
void U(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx!=fy)fa[fx]=fy;
}
2.加權並查集


· 最小生成樹

時間複雜度:
kruskal是O(eloge),樸素prime是O(n^2),prime+優先隊列是O(eloge)
稀疏圖用kruskal,稠密圖用prime+heap,空間足夠的情況下都用prime+heap。
(範圍太大的話就用配對堆?)

prime+heap

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5007,M=200007;
struct EDGE{int v,w,next;}e[M*2];//存邊
struct node{
	int v,w;
	node(int vv,int ww):v(vv),w(ww){};
	bool operator < (const node &o)const{
		return w>o.w;
	}
};//堆的類型 
int n,m,head[N],cnt,vis[N],d[N];//不加d[N]數組大數據可能會TLE
void addedge(int x,int y,int z){
	e[cnt].v=y;
	e[cnt].w=z;
	e[cnt].next=head[x];
	head[x]=cnt++;
}
priority_queue<node>q;
ll prime(int s){
	for(int i=1;i<=n;i++)d[i]=0x7fffffff;
	ll cost=0;
	node t(s,0);
	q.push(t);
	int Edgecnt=0;
	while(!q.empty()&&Edgecnt<=n){
		t=q.top();q.pop();
		if(vis[t.v])continue;
		vis[t.v]=1;
		cost+=t.w;
		Edgecnt++;
		for(int i=head[t.v];i!=-1;i=e[i].next)
			if(!vis[e[i].v]&&e[i].w<d[e[i].v]){
				q.push(node(e[i].v,e[i].w));
				d[e[i].v]=e[i].w;
			}
	}
	if(Edgecnt==n)return cost;
	else return -1;//非連通圖
}
int main(){
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		addedge(x,y,z);addedge(y,x,z);
	}
	ll ans=prime(1);
	if(ans!=-1)printf("%lld",ans);else printf("-1");
}

kruskal

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5007,M=200007;
struct EDGE{
	int u,v,w,next;
	bool operator <(const EDGE &o)const{
		return w<o.w;
	}
}e[M*2];//存邊
int n,m,cnt,fa[N];
void addedge(int x,int y,int z){
	e[cnt].u=x;
	e[cnt].v=y;
	e[cnt++].w=z;
}
int find(int u){
	if(fa[u]!=u)fa[u]=find(fa[u]);
	return fa[u];
}
ll kruskal(){
	sort(e,e+cnt);
	int Edgecnt=0;
	ll cost=0;
	for(int i=0;i<cnt;i++){
		int fu=find(e[i].u),fv=find(e[i].v);
		if(fu!=fv){
			cost+=e[i].w;
			fa[fu]=fv;
			Edgecnt++;
		}
		if(Edgecnt==n-1)break;
	}
	if(Edgecnt<n-1)return -1;//非連通
	else return cost;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		addedge(x,y,z);
	}
	ll ans=kruskal();
	if(ans!=-1)cout<<ans;
	else cout<<"orz";
}

prime+配對堆優化(待添加)


最短路

Floyd

多源最短路, 時間O(n^3) ,空間O(n^2)
計算所有點之間的最短路、可以計算負權圖不能存路徑

//鄰接矩陣存圖。
const ll Inf=0x7fffffff;
ll dis[N][N];
void Floyd(){
	for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(dis[i][j]>dis[i][k]+dis[k][j])
			dis[i][j]=dis[i][k]+dis[k][j];
}
int main(){
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)dis[i][j]=Inf; //初始化
	cin>>n;
	[...輸入鄰接矩陣]
	Floyd();
}

Dijkstra

計算一個點到其他所有點的最短路、適用於無負權圖可以存路徑
樸素版時間複雜度O(n^2),,優先隊列優化的O(eloge)

//樸素版(鄰接表存圖)
const int Inf=0x3f3f3f3f;
int n,m,s,t,head[N],vis[N],cnt,dis[N],path[N];//path記錄路徑
struct EDGE{int v,w,next;}e[M*2];
void Dijkstra(int s){	//s是起點
	for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);//初始化
	for(int i=1;i<=n;i++){
		int now,mn=Inf;
		for(int j=1;j<=n;j++)
			if(dis[j]<mn&&!vis[j])mn=dis[now=j]; 
		vis[now]=1;
		for(int j=head[now];j!=-1;j=e[j].next)
			if(dis[e[j].v]>dis[now]+e[j].w){
				dis[e[j].v]=dis[now]+e[j].w;
				//path[e[j].v]=now;	//記錄路徑
			}
	}
}
void print_path(int x){//輸出最短路徑,調用時參數爲終點 
	if(path[x]==0){
		printf("%d ",x);
		return;
	}
	print_path(path[x]);
	printf("%d ",x);
}
//Dijkstra+優先隊列
const int Inf=0x3f3f3f3f;
int n,m,s,t,head[N],vis[N],cnt,dis[N],path[N];//path記錄路徑
struct EDGE{int v,w,next;}e[M*2];
struct node{
	int v,d;
	node(int vv,int dd):v(vv),d(dd){};
	bool operator <(const node &o)const{
		return d>o.d;
	}
};//優先隊列類型
priority_queue<node>q; 
void Dijkstra(int s){
	for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);//初始化 
	q.push(node(s,0));
	while(!q.empty()){
		node now=q.top();q.pop();
		if(vis[now.v])continue;
		vis[now.v]=1; 
		for(int j=head[now.v];j!=-1;j=e[j].next)
			if(dis[e[j].v]>dis[now.v]+e[j].w){
				dis[e[j].v]=dis[now.v]+e[j].w;
				q.push(node(e[j].v,dis[e[j].v]));
				//path[e[j].v]=now.v;	//記錄路徑 
			}
	}
}
void print_path(int x){//調用時參數爲終點 
	if(path[x]==0){
		printf("%d ",x);
		return;
	}
	print_path(path[x]);
	printf("%d ",x);
}

SPFA

1. 時間複雜度比普通的Dijkstra和Ford低。期望值爲O(k*e)
(其中k爲所有頂點進隊的平均次數,e是邊的數量,可以證明k一般小於等於2)
2. 計算一個點到其他所有點的最短路,可以計算負權圖,能夠判斷是否夠有負環(存在點進隊超過n次即存在負環)

//鄰接表存圖
const ll Inf=2147483647;
struct EDGE{int v,w,next;}e[M*2];
int n,m,vis[N],times[N],head[N],cnt,path[N];//times記錄入隊次數,path記錄路徑
ll dis[N];
int spfa(int s){//返回-1則有負環,0則沒有 
	for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);
	vis[s]=1;	//times[s]=1;   //入隊次數 
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=0;			//釋放
		for(int i=head[now];i!=-1;i=e[i].next)
			if(dis[e[i].v]>dis[now]+e[i].w){ //鬆弛
				dis[e[i].v]=dis[now]+e[i].w;
				//path[e[i].v]=now;	//記錄路徑 
				if(!vis[e[i].v]){
					q.push(e[i].v);
					vis[e[i].v]=1;
					//times[e[i].v]++;
					//if(times[e[i].v]>n)return -1; //判負環
				}
			}
	}
	return 0;
}
void print_path(int x){//調用時參數爲終點 
	if(path[x]==0){
		printf("%d ",x);
		return;
	}
	print_path(path[x]);
	printf("%d ",x);
}

圖論方法記錄

1.正式比賽儘量用 dijkstra+優先隊列,別用spfa
2.有向圖求多點到單點的最短路徑,讓邊反向即可。
3.最小生成森林,可以建立一個超級源點,連向所有的點(邊權爲建立點需要的代價)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章