dijkstra 求最小环( CCPC桂林 - E. Buy and Delete )
题意经过转化后,本质就是求最小环。
有向图有以下三种实现方式,而无向图只能用第一种实现方式。
- 实现方式1:删边求最短距离
- 有向图实现方式2:回到起点构成环
- 有向图实现方法3:任意两点间的最短距离
删边求最短距离
这种实现方法有向图无向图都可用。
- 如果是无向图必须用这种
- 如果是有向图边数和点数差不多是也可以使用。
对于一条有向边 \((u,v)\),可以删掉这条边(不用这条边更新)跑从 v 到 u 的最短距离,加上这条边的权值便是该边所在的最小环。
对于每条边都求一次最短路,所以时间复杂度:\(O(m∗mlogn)\)。
有向图代码
有向图不需要删除边 \((a,b)\) ,因为是求的 \(dij(b,a)\) 会自动忽略边 \((a,b)\);
#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;
}
无向图代码
有向图需要对边 \((a,b)\) 进行continue处理
#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;
}
回到起点构成环
只能用于有向图,推荐使用这个,时间复杂度:\(O(n∗mlogn)\) 会比第一种速度快。
思想:
将每一个点都作为起点,跑 \(dijkstra(u,u)\)。
对于每个点,从该点出发判断是否能够再回到起点,如果能,说明从该点出发能构成环。
因为是跑最短路求得的,所以这个环是 所有从起点出发回到起点的环中 权值最小的一个。
将所有点作为起点所得的最小环的最小值 便是 整张图的最小环。
每个点都跑一遍 dij,所以 时间复杂度:\(O(n∗mlogn)\)。
有向图代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
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()
{
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);
cout << mina;
return 0;
}
任意两点间的最短距离
对于每个点都跑一遍最短路,便得到任意两点间的最短距离。
那么从 x 点到 y 点的最短距离 + 从 y 点到 x 点的最短距离 便构成了一个最小环。
二重遍历所有组合,取所有环中的最小值便是整张图的最小环。
每个点都求一次最短路,所以时间复杂度:\(O(n∗mlogn)\)。
有向图代码
#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;
}
E. Buy and Delete
方法1代码
因为没有学过dij求最小环,所以使用了第一种方法来求。结果被t疯了。后来面前通过剪枝通过。
代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2005, M = 5005;
const int INF = 0x3f3f3f3f;
int n, m, c;
int h[N], w[M], e[M], ne[M], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
void add(int u, int v, int ww)
{
e[idx] = v;
ne[idx] = h[u];
w[idx] = ww;
h[u] = idx++;
}
int ans = INF;
int dijkstra(int u, int v)
{
for (int i = 0; i <= n; i++)
dist[i] = INF;
for (int i = 0; i <= n; i++)
st[i] = 0;
dist[u] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, u}); // first存储距离,second存储节点编号
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver])
continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
if (dist[j] >= ans)
continue;
heap.push({dist[j], j});
}
}
}
if (dist[v] == INF)
return -1;
return dist[v];
}
signed main()
{
scanf("%d%d%d", &n, &m, &c);
for (int i = 0; i <= n; i++)
h[i] = -1;
int mi = INF;
for (int i = 0; i < m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
mi = min(w, mi);
}
if (mi > c)
return puts("0"), 0;
for (int i = 1; i <= n; i++)
{
for (int j = h[i]; j != -1; j = ne[j])
{
int sum = dijkstra(e[j], i);
/*重要剪枝*/
if(w[j]>=c) continue;
if (sum == -1)
continue;
ans = min(ans, sum + w[j]);
}
}
if (ans <= c)
puts("2");
else
puts("1");
}
方法2代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2005, M = 5005;
const int INF = 0x3f3f3f3f;
int n, m, c;
int h[N], w[M], e[M], ne[M], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
bool vis[N][N];
void add(int u, int v, int ww)
{
e[idx] = v;
ne[idx] = h[u];
w[idx] = ww;
h[u] = idx++;
}
int ans = INF;
void dijkstra(int u)
{
for (int i = 0; i <= n; i++)
dist[i] = INF;
for (int i = 0; i <= n; i++)
st[i] = 0;
dist[u] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, u}); // first存储距离,second存储节点编号
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver])
continue;
st[ver] = true;
// cout<<ver<<endl;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (j == u)
ans = min(ans, distance + w[i]);
//下一个节点是起点,那么环的大小就是起点到当前点距离+到起点这条边的权值。
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
}
signed main()
{
scanf("%d%d%d", &n, &m, &c);
for (int i = 0; i <= n; i++)
h[i] = -1;
int mi = INF;
for (int i = 0; i < m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
mi = min(w, mi);
}
if (mi > c)
return puts("0"), 0;
for (int i = 1; i <= n; i++)
{
dijkstra(i);
}
// cout<<ans<<endl;
if (ans <= c)
puts("2");
else
puts("1");
}