dijkstra 求最小环( CCPC桂林 - E. Buy and Delete )

前言
今天做题的时候,碰到一个有向图求最小环问题,发现除了经典的 Floyd求最小环 之外,熟知的求最短路问题的 dijkstra算法 也可以求最小环

有向图有以下三种实现方式,而无向图只能用第一种实现方式。


实现方式1:删边求最短距离

对于一条有向边 ( u , v ) (u, v) (u,v),可以删掉这条边(不用这条边更新)跑从 v 到 u 的最短距离,加上这条边的权值便是该边所在的最小环。

对于每条边都求一次最短路,所以时间复杂度: O ( m ∗ m l o g n ) O(m*mlogn) O(mmlogn)

这种实现方法有向图无向图都可用。

有向图 Code:

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

const int N = 50010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N], f[N];
vector<PII> e[N];
int mina = 1e9; //最小环权值 

struct node{
	int x, y, w;
}b[N];

int dij(int st, int en)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	
	for(int i=1;i<=n;i++) dist[i] = 1e10, f[i] = 0;
	dist[st] = 0;
	
	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.fi, w = it.se;
			
			if(dist[tx] > dist[x] + w)
				dist[tx] = dist[x] + w, que.push({dist[tx], tx});
		}
	}
}

signed main(){
	Ios;
	cin>>n>>m;
	
	int ans = 0;
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		
		b[i] = {x, y, z};
	}
	
	for(int i=1;i<=m;i++)
	{
		int x = b[i].x, y = b[i].y, w = b[i].w;
		dij(y, x);
		mina = min(mina, dist[x] + w); //从y到x的最短距离加上从x到y的这条边就是这个边所在的最小环 
	}
	
	cout << mina; 
	
	return 0;
}

无向图 Code:

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

const int N = 50010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N], f[N];
vector<PII> e[N];
int pre[N];

struct node{
	int x, y, w;
}b[N];

int dij(int st, int en)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	
	for(int i=1;i<=n;i++) dist[i] = 1e10, f[i] = 0, pre[i] = 0;
	dist[st] = 0;
	
	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.fi, w = it.se;
			if(x == st && tx == en || x == en && tx == st) continue; //st-en这条边不能走 
			
			if(dist[tx] > dist[x] + w)
				dist[tx] = dist[x] + w, que.push({dist[tx], tx}), pre[tx] = x;
		}
	}
}

signed main(){
	cin>>n>>m;
	
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		e[y].pb({x, z});
		
		b[i] = {x, y, z};
	}
	
	stack<int> stk;
	
	int mina = 1e9;
	for(int i=1;i<=m;i++)
	{
		int x = b[i].x, y = b[i].y, w = b[i].w;
		
		dij(y, x);
		if(dist[x] + w < mina){ //记录最小环中的所有点
			mina = dist[x] + w;
			while(stk.size()) stk.pop();
			
			int t = x;
			while(t) stk.push(t), t = pre[t];
		}
	}
	
	cout << mina << endl;
	if(!stk.size()) cout<<"No solution.";
	while(stk.size()) cout << stk.top() <<" ", stk.pop();
	
	return 0;
}

有向图实现方式2:回到起点构成环

将每一个点都作为起点,跑 dijkstra。
对于每个点,从该点出发判断是否能够再回到起点,如果能,说明从该点出发能构成环。
因为是跑最短路求得的,所以这个环是 所有从起点出发回到起点的环中 权值最小的一个
将所有点作为起点所得的最小环的最小值 便是 整张图的最小环。

每个点都跑一遍 dij,所以 时间复杂度 O ( n ∗ m l o g n ) O(n*mlogn) O(nmlogn)

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define pb push_back

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N], f[N];
vector<PII> e[N];
int mina = 1e18; //最小环大小

void dij(int st)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	for(int i=1;i<=n;i++) dist[i] = 1e9, f[i] = 0;
	dist[st] = 0;

	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.first, w = it.second;
			
			if(tx == st) mina = min(mina, dist[x] + w); //下一个节点是起点,那么环的大小就是起点到当前点距离+到起点这条边的权值。
			
			if(dist[tx] > dist[x] + w)
				dist[tx] = dist[x] + w, que.push({dist[tx], tx});
		}
	}
}

signed main(){
	Ios;
	cin>>n>>m;
	
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
	}
	
	cout << mina;
	
	return 0;
}

有向图实现方法3:任意两点间的最短距离

对于每个点都跑一遍最短路,便得到任意两点间的最短距离。

那么从 x 点到 y 点的最短距离 + 从 y 点到 x 点的最短距离 便构成了一个最小环。
二重遍历所有组合,取所有环中的最小值便是整张图的最小环。

每个点都求一次最短路,所以时间复杂度: O ( n ∗ m l o g n ) O(n * mlogn) O(nmlogn)

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second

const int N = 5010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N][N], f[N];
vector<PII> e[N];

int dij(int st)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	
	for(int i=1;i<=n;i++) dist[st][i] = 1e10, f[i] = 0;
	dist[st][st] = 0;
	
	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.fi, w = it.se;
			
			if(dist[st][tx] > dist[st][x] + w)
				dist[st][tx] = dist[st][x] + w, que.push({dist[st][tx], tx});
		}
	}
}

signed main(){
	Ios;
	cin>>n>>m;
	
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
	}
	
	for(int i=1;i<=n;i++) dij(i);
	
	int mina = 1e9;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i!=j) mina = min(mina, dist[i][j] + dist[j][i]);
		}
	
	cout << mina;
	
	return 0;
}

例题

最后附上这道题目~

题意:
A 和 B 正在玩一个游戏,给出一张图,初始只有 n 个点,没有边。

  • 起初 A 可以购买一些有向边加到图中,A 有总钱数 C C C,一共 m 条边,每条边有价格 w i w_i wi
  • 接下来到 B 操作,B 可以通过若干轮删边将所有边都删掉,每轮删边要满足:删掉的这些边在图中不构成环。

A 想要 B 删边的轮数尽量多,而 B 想删边的轮数尽量少。两者都采取最佳策略,问 B 要删边多少轮?

2 ≤ n ≤ 2000 , 1 ≤ m ≤ 5000 , 1 ≤ c ≤ 1 0 9 2≤n≤2000 , 1≤m≤5000, 1≤c≤10^9 2n2000,1m5000,1c109
1 ≤ u i , v i ≤ n , u i ≠ v i , 1 ≤ w i ≤ 100000 1≤u_i,v_i≤n, u_i≠v_i, 1≤w_i≤100000 1ui,vin,ui=vi,1wi100000

分析:
如果若干条边能够构成环的话,B 就需要两次操作才能将这个环删除:一次操作取若干条边破坏环,第二次操作删掉剩余边。

接下来就想错了,就想着 A 要使得图中的环尽可能多,然后 B 的操作次数就是 环数 + 1,然后就想着如何选择能够构成环最多,想到缩点等等。。整个思路就偏了。

无论是一个环 还是 无数个环,B 只需要两次操作就能将所有边删掉:一次操作取所有环中的若干条边,破坏所有的环,第二次操作删除所有剩余边。
所以:只要有环,B 都只需要 2 次操作。
如果没有环,只有边,那么 B 只需要 1 次操作;
如果没有边,那么 B 需要 0 次操作。

所以,如果 A 所拥有的钱数能够购买一个环,就能使得 B 的操作次数为 2。
可以将所有边都先加到图中,判断图中价格最小的一个环的价格,看是否不超过 C。
也就是,求有向图的最小环

那么就可以用有向图的三种实现方法。
但是,对于第一种实现方法,时间复杂度为 O(m*mlogn),在这道题中会超时。
只能用第二种或者第三种实现方法。

实现二:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

map<int,int> mp;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N], f[N];
vector<PII> e[N];
int mina = 1e18;

void dij(int st)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	for(int i=1;i<=n;i++) dist[i] = 1e9, f[i] = 0;
	dist[st] = 0;

	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.first, w = it.second;
			
			if(tx == st) mina = min(mina, dist[x] + w);
			
			if(dist[tx] > dist[x] + w)
				dist[tx] = dist[x] + w, que.push({dist[tx], tx});
		}
	}
}

signed main(){
	Ios;
	cin>>n>>m>>c;
	
	int ans = 0;
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		
		if(z <= c) ans = 1;
	}
	
	for(int i=1;i<=n;i++) dij(i);
	if(mina <= c) ans = 2;
	
	cout << ans;
	
	return 0;
}

实现三:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

map<int,int> mp;

const int N = 5010, mod = 1e9+7;
int T, n, m, k;
int a[N], c;
int dist[N][N], f[N];
vector<PII> e[N];

int dij(int st)
{
	priority_queue<PII, vector<PII>, greater<PII> > que;
	que.push({0, st});
	
	for(int i=1;i<=n;i++) dist[st][i] = 1e10, f[i] = 0;
	dist[st][st] = 0;
	
	while(que.size())
	{
		int x = que.top().se;
		que.pop();
		
		if(f[x]) continue;
		f[x] = 1;
		
		for(auto it : e[x])
		{
			int tx = it.fi, w = it.se;
			
			if(dist[st][tx] > dist[st][x] + w)
				dist[st][tx] = dist[st][x] + w, que.push({dist[st][tx], tx});
		}
	}
}

signed main(){
	Ios;
	cin>>n>>m>>c;
	
	int ans = 0;
	for(int i=1;i<=m;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		
		if(z <= c) ans = 1;
	}
	
	for(int i=1;i<=n;i++) dij(i);
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i!=j && dist[i][j] + dist[j][i] <= c) ans = 2; 
		}
		
	cout << ans;
	
	return 0;
}

以前只知道 Floyd求最小环,没想到 dijkstra求最小环 的时间效率甚至更优。

如果哪里有问题的话欢迎留言讨论~

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值