目录
前言
- 之前的博客用富文本写的,现在(手动emm,顺带复习一下)转换过来。
- 不止最短路,还有求环啥的
- 因为是主要是针对自己用的,所以很多自己已经熟悉的东西就没敲板子了,比如说dijkstra的朴素算法。
- 参考博客:
一、单源最短路
dijkstra算法
- 先上个图:
- 操作(与Prim很相似):dis数组初始化为
inf
,dis[s]=0
(以下假设s
为1)。然后每次循环找到没有标记而且 dis 值最小的点对它邻接的点进行松弛操作,同时标记该点(证明该点不可能再减小:它已经是能到达的点中dis
最小的了,如果想让其他店对它松弛操作之后还能减小,那是不可能的!)每次松弛操作:if(dis[u]+w<dis[v]) dis[v]=dis[u]+w
。 - 注意:不能处理带负权的图。
堆优化模板(时间复杂度 O ( m log m ) O(m\log m) O(mlogm))(找最小的dis以及其下标)
- 注意什么时候标记vis[i]=1,应该在遍历一个 i 之后标记,写在遍历前也不影响——总之要理解。
- 代码:
/*
1.题目链接:[P4779 【模板】单源最短路径(标准版)](https://www.luogu.com.cn/problem/P4779)
a.注意是有向边
*/
#include <bits/stdc++.h>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 2e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int fi, se;
pii() {}
pii(int _fi, int _se) { fi = _fi, se = _se; }
bool operator<(pii b) const { return fi > b.fi; }
};
priority_queue<pii> q;
int n, m, s;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
void dijkstra(int s) {
for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0;
dis[s] = 0;
q.push(pii(0, s));
while (!q.empty()) {
pii now = q.top();
q.pop();
int d = now.fi, i = now.se; //距离和下标
if (vis[i])
continue; //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
vis[i] = 1;
for (auto j : g[i]) {
if (vis[j.to]) continue;
//松弛操作
if (d + j.w < dis[j.to]) {
dis[j.to] = d + j.w;
q.push(pii(dis[j.to], j.to));
}
}
}
}
signed main() {
read(n), read(m), read(s);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
g[u].pb(node(v, w));
}
dijkstra(s);
for (int i = 1; i <= n; i++) print(dis[i], ' ');
return 0;
}
- 其他优化:参考oi-wiki
- 暴力: O ( n 2 + m ) O(n^2+m) O(n2+m)
- 堆: O ( m log n ) O(m\log n) O(mlogn)
- 优先队列: O ( m log m ) O(m\log m) O(mlogm)
- zkw线段树: O ( m log n + n ) O(m\log n+n) O(mlogn+n)
- Fibonacci堆: O ( n log n + m ) O(n\log n+m) O(nlogn+m)
- 主要在于维护整个区间内最小的值以及它的小标,堆优化和优先队列时间复杂度差不多。
SPFA
- 介绍:对bellman_ford算法的一个队列优化。可用于求单源最短路。(可以处理求含负权的最短路,但是不能处理含负环的最短路)。
- 操作:类似于 bfs ,每次检查可以更新当时没在队列中的点放进去继续等待更新,等一个都不能更新的时候就自动出来了。操作也简单。
- 时间复杂度:稀疏图 O ( k m ) O(km) O(km), k k k为常数。稠密图中退化到 O ( n m ) O(nm) O(nm)
模板(放入队列中的同时vis[s]=1,取出的同时vis[now]=0,更新所有可以更新的,更新的时候放入所有能放入的即vis[x]=0的x)
/*
1.题目链接:(和上面一个题)
*/
#include <bits/stdc++.h>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 2e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
int n, m, s;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
void spfa() {
for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0;
dis[s] = 0;
queue<int> q;
q.push(s), vis[s] = 1;
while (!q.empty()) {
int x = q.front();
q.pop(), vis[x] = 0; //毕竟一般也不会有自环
for (auto i : g[x]) {
/* if
* (vis[i.to])continue;不能这样,这个算法在队列中不代表不能够优化!——应该放在下面!*/
if (dis[x] + i.w < dis[i.to]) {
dis[i.to] = dis[x] + i.w;
if (!vis[i.to]) q.push(i.to), vis[i.to] = 1;
}
}
}
}
signed main() {
read(n), read(m), read(s);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
g[u].pb(node(v, w));
}
spfa();
for (int i = 1; i <= n; i++) print(dis[i], ' ');
return 0;
}
二、全源最短路
Floyd
- 介绍:可以处理求含负权的最短路,但是不能处理含负环的最短路。(主要是负环没有最短路emmm)
- 拓展:传递闭包,最长/短路,最小/“大”瓶颈路,判正负环。最小环?
模板(无脑三重循环)
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
三、传递闭包
- 介绍:
mp[i][j]=1
表示存在 i i i到 j j j的路径。floyd求出所有的mp[i][j]
。
模板
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) mp[i][j] |= mp[i][k] & mp[k][j];
四、最短路径树(应用)
- 概念:最短路构成的一棵树(一个概念罢了,拓展学不是很强)。
最短路径树计数
- 题目链接:黑暗城堡 LibreOJ - 10064
- 题意: n ( 1 ≤ n ≤ 1000 ) n(1\le n\le 1000) n(1≤n≤1000)个点,m条无向边,每条边长度为 l i ( 1 ≤ l ≤ 200 ) l_i(1\le l\le200) li(1≤l≤200),求不同最短路径树(最开始从1出发)的数量(结果对 2 31 − 1 2^{31}-1 231−1取余)。
- 题解:求出每个点的前一个节点的数量(满足对短路,即
dis[u]+w==dis[v]
,dis[i]表示节点s到i的最短距离),最后相乘取余。 - 操作:在松弛操作的时候操作(spfa的话建议用第二种操作,第一种或多或少有些问题!)或者在求出所有点的最短路之后操作。
模板(最短路径数计数模板题):
/*
1.题目就是上面的【黑暗城堡】
*/
/*操作1:在松弛操作的地方做修改*/
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
// #define ll long long
#define pb push_back
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 1e6 + 10;
const int inf = 2147483647; // 1<<31-1
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int fi, se;
pii() {}
pii(int _fi, int _se) { fi = _fi, se = _se; }
bool operator<(pii b) const { return fi > b.fi; }
};
priority_queue<pii> q;
int n, m;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn], pre[maxn];
void dijkstra() {
for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0, pre[i] = 0;
dis[1] = 0, pre[1] = 1;
q.push(pii(0, 1));
while (!q.empty()) {
pii now = q.top();
q.pop();
int d = now.fi, i = now.se; //距离和下标
if (vis[i])
continue; //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
vis[i] = 1;
for (auto j : g[i]) {
if (vis[j.to]) continue;
//松弛操作
if (d + j.w == dis[j.to]) pre[j.to]++; //操作1第一步
if (d + j.w < dis[j.to]) {
dis[j.to] = d + j.w;
q.push(pii(dis[j.to], j.to)), pre[j.to] = 1; //操作一第二步
}
}
}
}
signed main() {
read(n), read(m);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
g[u].pb(node(v, w)), g[v].pb(node(u, w));
}
dijkstra();
int ans = 1;
// for (int i = 1; i <= n; i++) cout << i << ":::" << pre[i] << endl;
for (int i = 1; i <= n; i++) ans = (ans * pre[i]) % inf;
print(ans, '\n');
return 0;
}
去掉途中一条边之后最短路径树大小(dis之和)是否有变化
- 题意: n n n个城市 ( n ≤ 100 ) (n\le 100) (n≤100), m m m条无向边,每两个城市之间都走最小路。从小到大打印所有公路序号,这些公路满足:去掉之后至少有两个城市之间的路程变大了。
- 题解:暴力每个起点,分别建最短路径树,然后枚举这树上的每条边,不经过这条边建一颗最短路径树,求出该起点到其他每个点的最短路径和,如果大于没去掉这条边之前的值,一定满足条件。说明,枚举任意起点的时候可以建任意一颗最短路径树,因为如果存在多颗最短路径树,同时枚举的某条边如果可以替代,那么他们是可以相互替代的,去掉任何一颗都不会改变最短路径和!!!
- 代码:就附上了,知道思路即可,做法仍是最短路+建最短路径树。
暂时就以上两个应用吧,毕竟自己没遇到过
五、最短路计数
- 概念:字面意思。
- 操作:与最短路径树的操作几乎一样,只需要做一些改动:pre初始化为0;
pre[s]=1
;然后每次遇到dis[u]==dis[v]
的时候pre[v]+=pre[u]
;松弛操作的时候pre[v]=pre[u]
就ok了。 - 对比最短路径树:pre初始化为0;
pre[s]=1
;然后每次遇到dis[u]==dis[v]
的时候pre[v]++
;松弛操作的时候pre[v]=1
就ok了。 - 例题:P1608 路径统计
- 题意:n 个点,最开始在1,要到达n。问最短路径是多少,有多少条。如果一条都没有,就打印no。 n ≤ 2000 , m ≤ n ∗ ( n − 1 ) n\le 2000,m\le n*(n-1) n≤2000,m≤n∗(n−1),保证无自环,不保证无重边,另外所有路是单向的!——重边取最小值,而不是如果想等的有 x 条就表示这里有 x 种走法。
模板
void dijkstra() {
for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0, pre[i] = 0;
dis[1] = 0, pre[1] = 1;
q.push(pii(0, 1)); //小顶堆
while (!q.empty()) {
pii now = q.top();
q.pop();
int d = now.fi, i = now.se; //距离和下标
if (vis[i])
continue; //之前标记过的点仍然在优先队列中取不出来很正常,到时候取出来
vis[i] = 1;
for (auto j : g[i]) {
if (vis[j.to]) continue;
//松弛操作
if (d + j.w == dis[j.to]) pre[j.to] += pre[i]; //操作一第一步
if (d + j.w < dis[j.to]) {
dis[j.to] = d + j.w;
q.push(pii(dis[j.to], j.to)),
pre[j.to] = pre[i]; //操作一第二步
}
}
}
// dis[i]==inf,或者说pre[n]=0时表示无路径
(dis[n] == inf) ? puts("No answer")
: (print(dis[n], ' '), print(pre[n], '\n'));
}
六、分层图最短路(用分层图的几种情况)
- 参考博客:
- 介绍:只是在建图的时候不一样,除此之外就是跑个普通最短路。当然,难的也就是建图。
用分层图的几种情况
-
k 个不同集合的边。将每个集合建一层图,然后第 k+1 层建一个虚图(用于连接每一层,价值根据题意)。如小雨坐地铁。
-
有 k 个机会免费走一条路。建 k+1 个相同的图,每层之间用有边的点连接起来,代价为0。没走一层表示用了一次机会。如P4568 [JLOI2011]飞行路线。
-
有k个机会逆向行驶。建 k+1 个相同的图,每层之间用有边的两点之间的逆向边连接,权值不变,没走一层表示用了一次机会(这里应该必须是有向图罢,不然没啥意义)。
例题1:小雨坐地铁
- 题目描述: n ( 1 − 1000 ) n(1-1000) n(1−1000)个车站, m ( 1 − 500 ) m(1-500) m(1−500)条地铁线。每次经过第 i 条地铁线需要花费 a i ( 1 − 100 ) a_i(1-100) ai(1−100),在这条地铁线上,每经过一个车站多花费 b i ( 1 − 100 ) b_i(1-100) bi(1−100),每条线上有 c i ( 1 − n ) c_i(1-n) ci(1−n)个车站(按顺序依次输入沿途站,注意是双向的)。
- 题解:情况1。注意进入第 m+1 层的时候花费 0 ,从 m+1 层进入的时候才花费 a x ax ax(相当于从nm+s到nm+t)。多读题,图别检错;注意建图之后点的总数。
- 代码:
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 500 * 1000 + 1000 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int fi, se; // dis,id
pii() {}
pii(int _fi, int _se) { fi = _fi, se = _se; }
bool operator<(pii b) const {
return fi > b.fi;
} //平常的就大顶堆,否则小顶堆。这里小顶堆
};
int n, m, s, t;
int a, b, c;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
priority_queue<pii> q;
void dijkstra(int s) {
for (int i = 1; i <= n * m + n; i++) dis[i] = inf;
dis[s] = 0, vis[s] = 1;
q.push(pii(0, s));
while (!q.empty()) {
pii now = q.top();
q.pop();
int x = now.se, w = now.fi;
for (auto i : g[x]) {
if (i.w + dis[x] < dis[i.to]) {
dis[i.to] = i.w + dis[x], vis[i.to] = 1;
q.push(pii(dis[i.to], i.to));
}
}
}
}
signed main() {
read(n), read(m), read(s), read(t);
for (int i = 1; i <= m; i++) {
read(a), read(b), read(c);
int now, pre;
//建第k张图
for (int j = 1; j <= c; j++) {
read(now);
if (j > 1) {
int u = (i - 1) * n + pre, v = (i - 1) * n + now, w = b;
g[u].pb(node(v, w));
g[v].pb(node(u, w));
}
//连虚图(相当于中转站)
int u = (i - 1) * n + now, v = n * m + now, w = a;
g[u].pb(node(v, 0)); //进入需要0
g[v].pb(node(u, w)); //出来代表从一条线进入新的一条线(每次都要花钱)
pre = now;
}
}
dijkstra(n * m + s); //从n*m+t出来
// for (int i = 1; i <= 10; i++) print(dis[i], ' ');
// cout << endl;
if (dis[n * m + t] == inf)
puts("-1");
else
print(dis[n * m + t], '\n');
return 0;
}
题目2:P4568 [JLOI2011]飞行路线
- 题目链接:P4568 [JLOI2011]飞行路线
- 题意: n ( 2 − 1 e 4 ) n(2-1e4) n(2−1e4)个点, m ( 1 − 5 e 4 ) m(1-5e4) m(1−5e4)条无向带权边(权值大小在 0 − 1 e 3 0-1e3 0−1e3),可以最多坐 k ( 0 − 10 ) k(0-10) k(0−10)次免费航班。问从 s 到 t 的最小花费。
- 题解:情况2 。难点在建图上
- 代码:
#include <bits/stdc++.h>
#define int long long
#define read(x) scanf("%lld", &x)
#define print(a, c) printf("%lld%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e4 * 12 + +10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int fi, se; // dis,id
pii() {}
pii(int _fi, int _se) { fi = _fi, se = _se; }
bool operator<(pii b) const {
return fi > b.fi;
} //正常为大顶堆,不正常为小顶堆
};
int n, m, k, s, t;
int u, v, w;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
priority_queue<pii> q;
void dijkstra(int s) {
for (int i = 0; i <= n * k + n; i++) dis[i] = inf;
dis[s] = 0, vis[s] = 1;
q.push(pii(0, s));
while (!q.empty()) {
pii now = q.top();
q.pop();
int x = now.se, w = now.fi;
for (auto i : g[x]) {
if (i.w + dis[x] < dis[i.to]) {
dis[i.to] = i.w + dis[x], vis[i.to] = 1;
q.push(pii(dis[i.to], i.to));
}
}
}
}
signed main() {
read(n), read(m), read(k);
read(s), read(t);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
// 0层图
g[u].pb(node(v, w)), g[v].pb(node(u, w));
for (int j = 1; j <= k; j++) {
// 1~k层图
g[j * n + u].pb(node(j * n + v, w));
g[j * n + v].pb(node(j * n + u, w));
//上一层与这一层连接
g[(j - 1) * n + u].pb(node(j * n + v, 0));
g[(j - 1) * n + v].pb(node(j * n + u, 0));
}
}
//传到n*k+t,最后直接取dis[n*k+t];
for (int i = 1; i <= k; i++) g[(i - 1) * n + t].pb(node(i * n + t, 0));
dijkstra(s); //从n*m+t出来
// for (int i = 0; i <= 10; i++) print(dis[i], ' ');
// cout << endl;
print(dis[n * k + t], '\n');
return 0;
}
/*
5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100
*/
情况3:与情况2的差别只在建图,有这个意识就好。
- ps:也不止与这些应用。
七、k短路(模板题)
- 题目链接:Remmarguts’ Date POJ - 2449
- 题意:N ( 1 ≤ N ≤ 1000 ) (1\le N\le 1000) (1≤N≤1000)个点,M ( 1 ≤ M ≤ 1 e 5 ) (1\le M\le 1e5) (1≤M≤1e5)条有向带权边,问点 s 到 t 的第 k $ ( 1 ≤ k ≤ 1000 ) (1\le k\le 1000) (1≤k≤1000)短路的长度。
- 题解:k短路模板题。首先建一个反向路,
dis[i]
表示到 t 的最短路,然后 bfs ,得到一次 t 记一次数。bfs 具体操作见代码。 - 注意:
- s 可能到不了 t。
- s 到 t可能没有 k 条路(s-t的任意路径上有环的话,s 到 t 的路可以视作有无数条)。
s==t
的时候 k 要加上1 ,因为 bfs 的时候最开始的 s 被算作 t(长度为 0 )。
模板1
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int i, dis, dis1;
pii() {}
pii(int _i, int _dis, int _dis1) { i = _i, dis = _dis, dis1 = _dis1; }
bool operator<(pii b) const { return dis + dis1 > b.dis + b.dis1; }
};
int n, m, s, t, k;
int u, v, w;
vector<node> g[maxn], re[maxn];
int dis[maxn], vis[maxn];
void spfa(int s) {
for (int i = 0; i <= n; i++) dis[i] = inf;
dis[s] = 0;
queue<int> q;
q.push(s), vis[s] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
// sbpoj,c++11都不支持
for (int j = 0; j < re[x].size(); j++) {
node i = re[x][j];
if (i.w + dis[x] < dis[i.to]) {
dis[i.to] = i.w + dis[x];
if (vis[i.to] == 0) q.push(i.to), vis[i.to] = 1;
}
}
}
}
priority_queue<pii> q;
void bfs(int s) {
q.push(pii(s, 0, 0));
int cnt = 0;
while (!q.empty()) {
pii now = q.top();
q.pop();
if (now.i == t) {
cnt++;
if (cnt == k) {
print(now.dis, '\n');
return;
}
}
for (int j = 0; j < g[now.i].size(); j++) {
node i = g[now.i][j];
q.push(pii(i.to, now.dis + i.w, dis[i.to]));
}
}
puts("-1"); //这是什么情况?——毕竟单向路径,可能s->t都没有k条路
}
signed main() {
read(n), read(m);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
g[u].pb(node(v, w));
re[v].pb(node(u, w));
}
read(s), read(t), read(k);
spfa(t);
if (dis[s] == inf)
puts("-1");
else {
if (s == t) k++; //为啥。看bfs哪里。s也被视作t了,这个t不算
bfs(s);
}
return 0;
}
/*
input:::
2 2
1 2 5
2 1 4
1 2 2
output:::
14
*/
模板2(要求输出前 k 小的数的话)
#include <bits/stdc++.h>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, x) memset(a, x, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
struct pii {
int i, dis, dis1;
pii() {}
pii(int _i, int _dis, int _dis1) { i = _i, dis = _dis, dis1 = _dis1; }
bool operator<(pii b) const { return dis + dis1 > b.dis + b.dis1; }
};
int n, m, k;
int u, v, w;
vector<node> g[maxn], re[maxn];
vector<int> ans;
int dis[maxn], vis[maxn];
void spfa(int s) {
for (int i = 0; i <= n; i++) dis[i] = inf;
dis[s] = 0;
queue<int> q;
q.push(s), vis[s] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (int j = 0; j < re[x].size(); j++) {
node i = re[x][j];
if (i.w + dis[x] < dis[i.to]) {
dis[i.to] = i.w + dis[x];
if (vis[i.to] == 0) q.push(i.to), vis[i.to] = 1;
}
}
}
}
priority_queue<pii> q;
void bfs(int s) {
q.push(pii(s, 0, 0));
int cnt = 0;
while (!q.empty()) {
pii now = q.top();
q.pop();
if (now.i == 1) {
cnt++;
ans.pb(now.dis);
if (cnt == k) return;
}
for (int j = 0; j < g[now.i].size(); j++) {
node i = g[now.i][j];
q.push(pii(i.to, now.dis + i.w, dis[i.to]));
}
}
while (ans.size() < k) ans.pb(-1);
}
signed main() {
read(n), read(m), read(k);
for (int i = 1; i <= m; i++) {
read(u), read(v), read(w);
g[u].pb(node(v, w));
re[v].pb(node(u, w));
}
spfa(1);
if (dis[n] == inf)
for (int i = 1; i <= k; i++) ans.pb(-1);
else {
if (n == 1) k++; //为啥。看bfs哪里。s也被视作t了,这个t不算
bfs(n);
}
for (auto i : ans) print(i, '\n');
return 0;
}
/*
input:::
5 8 7
5 4 1
5 3 1
5 2 1
5 1 1
4 3 4
3 1 1
3 2 1
2 1 1
output:::
1
2
2
3
6
7
-1
*/
八、求两对点的最短路的最大重合路径长度(遇到再说罢)
写在最后
现在可能还欠缺的知识:最小环,求两队点的最短路的最大重合路径长度。另一个博客独有的?判正环,负环,“最大瓶颈路”/最小瓶颈路,差分约束等等。还挺多没涉及到的emmm。恕我直言,凭我的废物属性,大概要三五个月下一轮学习的时候才会把两个博客整合(+补充欠缺的知识emmm)。现在拉拉进度罢先。
九、其他相关操作
模板1:求环(DFS求环模板题)
- 题目链接:F. Forest Program
- 题意:n 个点,m 条无向边。无自环无重边,且每条边最多被一个环公用。求能得到的森林种类数。
- 题解:每个环至少删去一条边,其他不成环的边可删可不删。主要是求环。
- 求环:DFS求环,从一个点出发,沿途点记录深度,如果DFS的时候遇到深度更小且不为-1的点则记录环(环的大小
dep[x]-dep[i]+1
)。
- 求环:DFS求环,从一个点出发,沿途点记录深度,如果DFS的时候遇到深度更小且不为-1的点则记录环(环的大小
- 代码:
#include <bits/stdc++.h>
// #define int long long
#define ll long long
#define pii pair<int, int>
#define x first
#define y second
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 5e5 + 10;
const int mod = 998244353;
int n, m, u, v;
vector<int> g[N], vec, ans;
int fa[N], dep[N];
int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
// x = find(x), y = find(y);
// if (x != y) fa[x] = fa[y];
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
void dfs(int x, int Fa) {
// cout << "### " << x << " " << Fa << endl;
for (auto i : g[x]) {
if (i == Fa) continue; //无重边
if (dep[i] != -1) {
if (dep[i] < dep[x]) ans.push_back(dep[x] - dep[i] + 1);//自己模拟一遍,dep[i]!=-1不止是dep[i]<dep[x],还有可能从i遍历到x,那么swap(i,x),dep[i]<dep[x]。
continue;
}
dep[i] = dep[x] + 1;
dfs(i, x);
}
}
ll Bit[N];
void init(int n) {
Bit[0] = 1;
for (int i = 1; i <= n; i++) Bit[i] = Bit[i - 1] * 2 % mod;
}
signed main() {
init(N - 1);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u, &v);
// cout << ">>>" << u << " " << v << endl;
g[u].push_back(v), g[v].push_back(u);
// merge(u, v);
if (find(u) != find(v)) merge(u, v);
}
for (int i = 1; i <= n; i++) vec.push_back(find(i));
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
memset(dep, -1, sizeof(dep));
for (auto i : vec) {
// dbg(i);
// i=2;
dep[i] = 0;
dfs(i, 0);
}
// for (auto i : ans) cout << i << " ";
// cout << endl;
ll sum = 1, cnt = m;
for (auto i : ans)
cnt -= 1LL * i, sum = sum * ((Bit[i] - 1LL) % mod + mod) % mod;
// dbg(sum), dbg(cnt);
sum = sum * Bit[cnt] % mod;
// cout << ">>>";
cout << sum << endl;
return 0;
}
/*
Examples
inputCopy
3 3
1 2
2 3
3 1
outputCopy
7
inputCopy
6 6
1 2
2 3
3 1
2 4
4 5
5 2
outputCopy
49
*/