acwing提高最短路
单源最短路的建图方式
920. 最优乘车

思路
1.输入:由于不知道一条巴士路线的车站数量所以需要用到getline();
它有两种形式:一种是头文件< istream >中输入流成员函数;一种在头文件< string >中普通函数;
< istream >中的getline
cin.getline(char* s, streamsize n );
// 从istream中读取至多n个字符(包含结束标记符)保存在s对应的数组中。即使还没读够n个字符,注意是char[]
cin.getline(char*s, streamsize n, char delim);
//如果遇到delim 或 字数达到限制,则读取终止,注意delim都不会被保存进s对应的数组中。
< string > 中的getline
getline (istream& is, string& str);
// is :表示一个输入流,例如 cin; str :string类型的引用,用来存储输入流中的流信息。
getline (istream& is, string& str, char delim);
//delim :char类型的变量,所设置的截断字符;在不自定义设置的情况下,遇到'\n',则终止输入
< sstream > 中的stringstream
istringstream::istringstream(string str);
istringstream >> t //向流中传值
istringstream << result; //向result中传值
//stringstream 对象用于输入一行字符串,以 空格 为分隔符把该行分隔开来
2.换乘多少次 可以转换为 乘坐过多少次车减1
1、在同一条路线中,任意一个在此路线上的车站均能沿着该路线的方向到达后面的车站,权值都是1,表示只乘坐一次车
2、通过建图,由于权值均是1,使用bfs求出1号点到n号点最少乘过多少次车。
输入样例:
3 7
6 7
4 7 3 6
2 1 3 5
输出样例:
2
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
#include <sstream>
using namespace std;
const int N = 510;
int m, n;
bool g[N][N];
int dist[N];
int stop[N];
int q[N];
void bfs()
{
int hh = 0, tt = 0;
memset(dist, 0x3f, sizeof dist);
q[0] = 1;
dist[1] = 0;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = 1; i <= n; i ++ )
if (g[t][i] && dist[i] > dist[t] + 1)
{
dist[i] = dist[t] + 1;
q[ ++ tt] = i;
}
}
}
int main()
{
cin >> m >> n;
string line;
getline(cin, line); //读掉第一个回车
while (m -- )
{
getline(cin, line); //读入一行
stringstream ssin(line);
int cnt = 0, p;
while (ssin >> p) stop[cnt ++ ] = p;
for (int j = 0; j < cnt; j ++ )
for (int k = j + 1; k < cnt; k ++ )
g[stop[j]][stop[k]] = true;
}
bfs();
if (dist[n] == 0x3f3f3f3f) puts("NO");
else cout << max(dist[n] - 1, 0) << endl;
return 0;
}
903. 昂贵的聘礼

思路
1.酋长的地位不是最高的,所以还要枚举合法区间
2.建立虚拟原点 购买该物品一直优惠到物品1的最短路
输入样例:
1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0
输出样例:
5250
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];
int dijkstra(int down, int up)
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[0] = 0; //虚拟原点
for (int i = 1; i <= n + 1; i ++ )
{
int t = -1;
for (int j = 0; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
if (level[j] >= down && level[j] <= up)
dist[j] = min(dist[j], dist[t] + w[t][j]);
}
return dist[1];
}
int main()
{
cin >> m >> n;
memset(w, 0x3f, sizeof w);
for (int i = 1; i <= n; i ++ ) w[i][i] = 0; //每个点到自己的距离
for (int i = 1; i <= n; i ++ )
{
int price, cnt;
cin >> price >> level[i] >> cnt;
w[0][i] = min(price, w[0][i]); //直接买该物品
while (cnt -- )
{
int id, cost;
cin >> id >> cost;
w[id][i] = min(w[id][i], cost);
}
}
int res = INF;
//枚举区间
for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));
cout << res << endl;
return 0;
}
单源最短路的综合应用
1135. 新年好(dfs)

1.首先用spfa被卡了
2.
输入样例:
6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7
输出样例:
21
代码模板
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 50010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[6][N];
int source[6];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(int start, int dist[])
{
memset(dist, 0x3f, N * 4);
dist[start] = 0;
memset(st, 0, sizeof st);
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, start});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
}
int dfs(int u, int start, int distance)
{
if (u > 5) return distance;
int res = INF;
for (int i = 1; i <= 5; i ++ )
if (!st[i])
{
int next = source[i];
st[i] = true;
res = min(res, dfs(u + 1, i, distance + dist[start][next]));
st[i] = false;
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
source[0] = 1;
for (int i = 1; i <= 5; i ++ ) scanf("%d", &source[i]);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < 6; i ++ ) dijkstra(source[i], dist[i]);
memset(st, 0, sizeof st);
printf("%d\n", dfs(1, 0, 0));
return 0;
}
340. 通信线路(二分)

思路
题目转换为寻找 1-N 路径中第 K + 1 大最小的值,继续转换为:设定一个bound【0-1000001】,求1-N最少经过的长度大于x的边的数量是否小于等于k,如果小于k则bound还可以缩小,否则不可缩小,用二分寻找最小值。
所有边分类所有大于bound的边权为1,所有小于bound的边权为0,边权只有0-1,用双端队列。
时间复杂度O(n+m)logL = 200万左右
输入样例:
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
输出样例:
4
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 1010, M = 20010;
int n, m, k;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
deque<int> q;
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(int bound)
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
q.push_back(1);
dist[1] = 0;
while (q.size())
{
int t = q.front();
q.pop_front();
if (st[t]) continue;
st[t] = true;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i], x = w[i] > bound;
if (dist[j] > dist[t] + x)
{
dist[j] = dist[t] + x;
if (!x) q.push_front(j);
else q.push_back(j);
}
}
}
return dist[n] <= k;
}
int main()
{
cin >> n >> m >> k;
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
int l = 0, r = 1e6 + 1;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (r == 1e6 + 1) cout << -1 << endl;
else cout << r << endl;
return 0;
}
342. 道路与航线(拓扑排序)

思路
1.关于spfa它又死了,不然是一个spfa模板题
2.所以想到只能用dijkstra 但是不能处理负权边
3.首先道路: 无负权边,双向 ->可以用跑dijkstra
其次航道: 有负权边,单向,且不可能有其他道路回到原来的地方(无环)->可以用拓扑排序优化
4.所以代码实现:输入所有道路,把道路连通块(城市内)当作一个团,并标记记录相应得团中得点,和点是属于哪个团得,团和团之间航道相连;
5.输入所有航道,并记录入度,以便拓扑排序。
6.按照拓扑排序依次处理每个连通块,先将入度为0得连通块编号加入队列
7.队头取出一个连通块编号,将该连通块得所有点跑dijkstra
8.跑dijkstra得过程中如果出队得点是该连通块内部的点则更新,非连通块内部的点则该连通块入度减1,如果该连通块入度==0则将该连通块编号加入拓扑排序队列中......
9.刚开始觉得每次把所有连通块的所有点入队跑dijkstra感觉有点问题,但是dijkstra小根堆优化,基于贪心策略,出队的必然是确认最小的方案,而原本入队的INF的点由于小根堆优化,出队时必然早已被更新,判重直接continue,所以可以总结出小根堆优化dijkstra不知道起点再何处可以直接将整个连通块所有点入队 跑dijkstra不会有任何问题。
所以本题的key:根据拓扑排序的顺序,本题的负权边用拓扑排序优化,正权连通块用dijkstra即可
10.时间复杂度主要是dijkstra 即O(mlogn)
输入样例:
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
输出样例:
NO PATH
NO PATH
5
0
-95
-100
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 25010, M = 150010, INF = 0x3f3f3f3f;
int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int bid)
{
id[u] = bid, block[bid].push_back(u);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!id[j])
dfs(j, bid);
}
}
void dijkstra(int bid)
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (auto u : block[bid])
heap.push({dist[u], u});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y, distance = t.x;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (id[j] == id[ver]) heap.push({dist[j], j});
}
}
}
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 1; i <= bcnt; i ++ )
if (!din[i])
q.push(i);
while (q.size())
{
int t = q.front();
q.pop();
dijkstra(t);
}
}
int main()
{
cin >> n >> mr >> mp >> S;
memset(h, -1, sizeof h);
while (mr -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= n; i ++ )
if (!id[i])
{
bcnt ++ ;
dfs(i, bcnt);
}
while (mp -- )
{
int a, b, c;
cin >> a >> b >> c;
din[id[b]] ++ ;
add(a, b, c);
}
topsort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > INF / 2) cout << "NO PATH" << endl;
else cout << dist[i] << endl;
return 0;
}
341. 最优贸易

思路
(SPFA) O(n+km)
先求出:
从 11 走到 ii 的过程中,买入水晶球的最低价格 dmin[i];
从 ii 走到 nn 的过程中,卖出水晶球的最高价格 dmax[i];
然后枚举每个城市作为买卖的中间城市,求出 dmax[i] - dmin[i] 的最大值即可。
求 dmin[i] 和 dmax[i] 时,由于不是拓扑图,状态的更新可能存在环,因此不能使用动态规划,只能使用求最短路的方式。
另外,我们考虑能否使用 dijkstra 算法,如果当前 dmin[i] 最小的点是 5,那么有可能存在边 5-> 6, 6-> 7, 7-> 5,假设当前 dmin[5] = 10,则有可能存在 6 的价格是11, 但 7 的价格是3,那么 dmin[5] 的值就应该被更新成3,因此当前最小值也不一定是最终最小值,所以dijkstra算法并不适用,我们只能采用 spfa 算法。
时间复杂度
瓶颈是SPFA,SPFA 算法的时间复杂度是 O(km),其中 kk 一般情况下是个很小的常数,最坏情况下是 n, n 表示总点数,m 表示总边数。因此总时间复杂度是 O(km)。
输入样例:
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
输出样例:
NO PATH
NO PATH
5
0
-95
-100
代码模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 2e6 + 10, M = 1e5 + 10, INF = 0x3f3f3f3f;
int n,m;
int h[M],rh[N],ne[N],e[N],w[N],idx;
int buy[N],sell[N];
bool st[N];
void add(int *h, int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//买入的最小值
void spfa1()
{
queue<int> q;
memset(buy, INF, sizeof buy);
memset(st, false, sizeof st);
q.push(1);
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i] )
{
int j = e[i];
if(buy[j] > min(buy[t], w[j]))
{
buy[j] = min(buy[t], w[j]);
if(!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
}
//卖出的最大值
void spfa2()
{
queue<int> q;
memset(st, false, sizeof st);
q.push(n);
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = rh[t]; ~i; i = ne[i] )
{
int j = e[i];
if(sell[j] < max(sell[t],w[j]))
{
sell[j] = max(sell[t],w[j]);
if(!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> w[i];
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
while(m --)
{
int a,b,op;
cin >> a >> b >> op;
add(h, a, b), add(rh, b, a);
if (op == 2) add(h, b, a), add(rh, a, b);
}
spfa1();
spfa2();
int ans = 0;
for(int i = 1; i <= n; i ++ )
{
ans = max(ans, sell[i] - buy[i]);
}
cout << ans << endl;
return 0;
}
单源最短路的扩展应用
1131. 拯救大兵瑞恩(bfs + 状态压缩)

输入样例:
4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1
输出样例:
14
代码模板
//邻接表 + 双端队列
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 11, M = 360, P = 1 << 10;
int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P];
bool st[N * N][P];
set<PII> edges;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void build()
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
for (int u = 0; u < 4; u ++ )
{
int x = i + dx[u], y = j + dy[u];
if (!x || x > n || !y || y > m) continue;
int a = g[i][j], b = g[x][y];
if (!edges.count({a, b})) add(a, b, 0);
}
}
int bfs()
{
memset(dist, 0x3f, sizeof dist);
dist[1][0] = 0;
deque<PII> q;
q.push_back({1, 0});
while (q.size())
{
PII t = q.front();
q.pop_front();
if (st[t.x][t.y]) continue;
st[t.x][t.y] = true;
if (t.x == n * m) return dist[t.x][t.y];
if (key[t.x])
{
int state = t.y | key[t.x];
if (dist[t.x][state] > dist[t.x][t.y])
{
dist[t.x][state] = dist[t.x][t.y];
q.push_front({t.x, state});
}
}
for (int i = h[t.x]; ~i; i = ne[i])
{
int j = e[i];
if (w[i] && !(t.y >> w[i] - 1 & 1)) continue; // 有门并且没有钥匙
if (dist[j][t.y] > dist[t.x][t.y] + 1)
{
dist[j][t.y] = dist[t.x][t.y] + 1;
q.push_back({j, t.y});
}
}
}
return -1;
}
int main()
{
cin >> n >> m >> p >> k;
for (int i = 1, t = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
g[i][j] = t ++ ;
memset(h, -1, sizeof h);
while (k -- )
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
int a = g[x1][y1], b = g[x2][y2];
edges.insert({a, b}), edges.insert({b, a});
if (c) add(a, b, c), add(b, a, c);
}
build();
int s;
cin >> s;
while (s -- )
{
int x, y, c;
cin >> x >> y >> c;
key[g[x][y]] |= 1 << c - 1;
}
cout << bfs() << endl;
return 0;
}
//邻接矩阵 + bfs
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 105,M = 12;
typedef pair<int,int> PII;
int g[N][N],key[N],d[N][1<<M];
bool st[N][1<<M];
int n,m,p,k,s;
int dx[] = {0,1,0,-1};
int dy[] = {1,0,-1,0};
queue<PII> q;
int get(int x,int y){
return (x - 1) * m + y;
}
int bfs(){
int t = get(1,1);
q.push({t,key[t]});
st[t][key[t]] = true;
memset(d,0x3f,sizeof d);
d[t][key[t]] = 0;
while(q.size()){
PII u = q.front();
q.pop();
int z = u.first,v = u.second;
for(int i = 0;i < 4;i++){
int x = (z - 1) / m + dx[i] + 1,y = (z - 1) % m + dy[i] + 1;
int v1 = v,z1 = get(x,y);
if(!x || !y || x > n || y > m || !g[z][z1]) continue;
if(g[z][z1] != -1){
if(!(v >> g[z][z1] & 1)) continue;
}
v1 |= key[z1];
if(!st[z1][v1]){
q.push({z1,v1});
st[z1][v1] = true;
d[z1][v1] = d[z][v] + 1;
}
if(z1 == n * m) return d[z1][v1];
}
}
return -1;
}
int main(){
cin>>n>>m>>p;
cin>>k;
int x1,y1,x2,y2,z,z1,z2;
memset(g,-1,sizeof g);
while(k--){
cin>>x1>>y1>>x2>>y2>>z;
z1 = get(x1,y1),z2 = get(x2,y2);
g[z1][z2] = g[z2][z1] = z;
}
cin>>s;
while(s--){
cin>>x1>>y1>>z;
key[get(x1,y1)] |= 1 << z;
}
cout<<bfs()<<endl;
return 0;
}
1134. 最短路计数(最大值方案数)

思路
求条数的题目中一定不存在和小于等于0的环
将能更新前驱连起来 - > 拓扑序
bfs 每次扩展一层 -> 拓扑序
dijkstra 每个点只出队一次,且出队该点的cnt必然已经确定,即所有dist等于该点的前驱已经出队 -> 拓扑序
spfa 每个点可能入队出队多次不能保证拓扑序
例如:

思路
dijkstra 基于贪心策略,更新到6时候必然是由前驱5和3更新,7未被更新,6出队已经把所有min的前驱都更新完毕,cnt已经确定,即满足拓扑序
spfa 基于bellman-ford算法,6先被3更新,然后cnt还未确定就更新7,在等5更新6时候由于最小值相同不入队,7的cnt便无法更新,不满足拓扑序
本题由于边权都为1直接用bfs即可
输入样例:
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
输出样例:
1
1
1
2
4
代码模板
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 400010, mod = 100003;
int n, m;
int h[N], e[M], ne[M], idx;
int dist[N], cnt[N];
int q[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void bfs()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
cnt[1] = 1;
int hh = 0, tt = 0;
q[0] = 1;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + 1) //更新最大值
{
dist[j] = dist[t] + 1;
cnt[j] = cnt[t]; //当前得方案数 = 上结点得方案数
q[ ++ tt] = j;
}
else if (dist[j] == dist[t] + 1) //最大值相等-> 对方案数操作
{
cnt[j] = (cnt[j] + cnt[t]) % mod; //当前得方案数 + 上一节点得方案数
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bfs();
for (int i = 1; i <= n; i ++ ) printf("%d\n", cnt[i]);
return 0;
}
383. 观光(拆点dp求方案数)

思路
1.当前最短路变成次短路,更新最短路,将最短路和次短路加入优先队列,到达j的最短路个数和到达t是一样的
2.找到一条新的最短路,更新最短路条数,到达j的最短路个数应该加上到达t的最短路个数,从t经过的最短路,在j上经过的时候也是最短路
3.找到一条更短的次短路,覆盖掉当前次短路,加入优先队列,到达j的最短路个数和到达t是一样的
4.找到一条新的次短路,更新次短路条数,到达j的最短路个数应该加上到达t的最短路个数,从t经过的最短路,在j上经过的时候也是最短路
输入样例:
2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1
输出样例:
3
2
代码模板
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 1010,M = 10010;
int h[N],e[M],ne[M],w[M],idx;
//状态0表示的是求最下,状态1表示求的是次小
int cnt[N][2]; //cnt[i][0]表示当前到达节点是i,且求的是0状态下的所有路径中最短路径的边数
int dist[N][2]; //dist[i][0]表示当前到达节点是i,且求的是0状态下的最短路径的值
bool st[N][2]; //与上面同理
int n,m,S,T; //S表示起点,T表示终点
struct node{ //小根堆,重载大于号
int id,type,distance; //分别是编号,状态,和当前点到起点的最小或次小距离
bool operator> (const node& a) const{ //从大到小排序
return distance > a.distance;
}
};
void add(int a,int b,int c){
w[idx] = c;
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int dijkstra(){
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
priority_queue<node,vector<node>,greater<node>> heap;
dist[S][0] = 0;
cnt[S][0] = 1;
heap.push({S,0,0});
while(heap.size()){
node t = heap.top();
heap.pop();
int ver = t.id , type = t.type , distance = t.distance;
if(st[ver][type]) continue;
st[ver][type] = true;
for(int i = h[ver];i != -1;i = ne[i]){
int j = e[i];
//先考虑最短的情况(大于、等于)
if(dist[j][0] > dist[ver][type] + w[i]){
//dist[j][0]成为次小,先要赋值给dist[j][]中次小的状态
dist[j][1] = dist[j][0]; cnt[j][1] = cnt[j][0];
heap.push({j, 1, dist[j][1]}); //发生改变就要入队
dist[j][0] = dist[ver][type] + w[i]; cnt[j][0] = cnt[ver][type]; //直接转移
heap.push({j,0,dist[j][0]});
}else if(dist[j][0] == dist[ver][type] + w[i]){
cnt[j][0] += cnt[ver][type]; //从t经过的最短路,在j上经过的时候也是最短路
//轮到枚举次小
}else if(dist[j][1] > dist[ver][type] + w[i]){
dist[j][1] = dist[ver][type] + w[i];
cnt[j][1] = cnt[ver][type];
heap.push({j, 1, dist[j][1]});
}else if(dist[j][1] == dist[ver][type] + w[i]){
cnt[j][1] += cnt[ver][type]; //从t经过的最短路,在j上经过的时候也是最短路
}
}
}
int res = cnt[T][0];
//最后还要特判以下最小和次小的路径之间是否相差1符合要求
if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];
return res;
}
int main(){
int t;
cin >> t;
while(t--){
memset(h,-1,sizeof h);
cin >> n >> m;
for(int i = 0;i < m;++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
scanf("%d%d",&S,&T);
cout << dijkstra() << endl;
}
return 0;
}
Floyd
1125. 牛的旅行(基本应用)


思路
1.floyd算法求出任意两个连通点之间的距离
2.求出所有连通集合中的点到该点最远的距离
3.枚举所有非连通点maxd[i] + maxd[j] + dist[i,j] :
即i点在该连通集合中最远的距离 + j点在该连通集合中最远的距离 + i两点的j的实际距离
输入样例:
8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010
输出样例:
22.071068
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 155;
const double INF = 1e20;
int n;
PDD q[N];
double d[N][N];
double maxd[N];
char g[N][N];
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
for (int i = 0; i < n; i ++ ) cin >> g[i];
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (i == j) d[i][j] = 0;
else if (g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);
else d[i][j] = INF;
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
double r1 = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
if (d[i][j] < INF / 2)
maxd[i] = max(maxd[i], d[i][j]);
r1 = max(r1, maxd[i]);
}
double r2 = INF;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (d[i][j] > INF / 2)
r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));
printf("%.6lf\n", max(r1, r2));
return 0;
}
343. 排序(传递闭包)

思路
从性质出发解决问题
解法1:根据传递闭包的性质出发 : 所有间接可以连接的边都手动添加一条边直接连接,用Floyd解决
矛盾 : 存在环,即dist[i,i] == 0
无法确定关系: 即dist[i,j] == 0 && dist[j,i] == 0;
可以确定关系:除上述条件外
如何输出拓扑序
除了已经确定点,当该点没有点可以直接连接该点,那么该点即为~
解法2:根据拓扑排序的性质解决
矛盾 : 存在环,导致队列为空时候还有入度为大于0的未入队
无法确定关系:入队的个数超过1
可以确定关系:除上述条件外
输入样例:
4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0
输出样例:
Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.
代码模板1Floyd
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 26;
int n, m;
bool g[N][N], d[N][N];
bool st[N];
void floyd()
{
memcpy(d, g, sizeof d);
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] |= d[i][k] && d[k][j];
}
int check()
{
//是否矛盾
for (int i = 0; i < n; i ++ )
if (d[i][i])
return 2;
//能否确定关系
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
if (!d[i][j] && !d[j][i])
return 0;
return 1;
}
char get_min()
{
for (int i = 0; i < n; i ++ )
if (!st[i])
{
bool flag = true;
for (int j = 0; j < n; j ++ )
if (!st[j] && d[j][i])
{
flag = false;
break;
}
if (flag)
{
st[i] = true;
return 'A' + i;
}
}
}
int main()
{
while (cin >> n >> m, n || m)
{
memset(g, 0, sizeof g);
int type = 0, t; //1 可以确定关系 0不可以确定关系 2矛盾
for (int i = 1; i <= m; i ++ )
{
char str[5];
cin >> str;
int a = str[0] - 'A', b = str[2] - 'A';
if (!type) //如果已经矛盾或者已经可以确定关系可以continue
{
g[a][b] = 1;
floyd();
type = check();
if (type) t = i;
}
}
if (!type) puts("Sorted sequence cannot be determined.");
else if (type == 2) printf("Inconsistency found after %d relations.\n", t);
else
{
memset(st, 0, sizeof st);
printf("Sorted sequence determined after %d relations: ", t);
for (int i = 0; i < n; i ++ ) printf("%c", get_min());
printf(".\n");
}
}
return 0;
}
代码模板2拓扑排序
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
const int N = 30, M = 1e4;
int h[N],ne[M],e[M],idx;
int id[N],dist[N];
bool st[N][N],s[N];
int n,m,type,cnt;
// 0 无法确定关系 1 矛盾 2 可以确定关系
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
void topsort(int x,int d[])
{
string res;
bool flag = false;
queue<int> q;
for(int i = 0; i < n; i ++ )
if(d[i] == 0 )
q.push(i);
while(q.size())
{
if(q.size() > 1)
{
type = 0;
flag = true;
//无法确定关系
}
int t = q.front();
q.pop();
res += (t + 'A');
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
d[j]--;
if(d[j] == 0) q.push(j);
}
}
if(res.length() == n && !flag) //可以确定关系
{
type = 2;
cout << "Sorted sequence determined after " << x << " relations: " << res << ".\n";
}
if(res.length() < cnt) //矛盾
{
type = 1;
printf("Inconsistency found after %d relations.\n", x);
}
}
int main()
{
while(cin >> n >> m, n || m)
{
char str[5];
memset(h, -1, sizeof h);
memset(id, 0, sizeof id);
memset(st, false, sizeof st);
memset(s,false,sizeof s);
idx = 0;type = 0,cnt = 0;
for(int i = 1; i <= m; i ++ )
{
cin >> str;
int x = str[0] - 'A', y = str[2] - 'A';
add(x,y),st[x][y] = true;
id[y] ++;
if(!s[x]) cnt ++,s[x] = true;
if(!s[y]) cnt ++,s[y] = true;
memcpy(dist,id,sizeof dist);
if(!type) topsort(i,dist);
}
if(!type) printf("Sorted sequence cannot be determined.\n");
}
return 0;
}
344. 观光之旅(最小环)

思路

如图所示:k从小到大枚举,当枚举到k = 5时,1-4的最短路径为9,此时还未更新1-4经过5的最短路径 所以可以求出该环的大小即 1-4最短路径 + 1-5的边 + 5-4的边,以此类推... 记录路径:i < j < k ,i - j 的最短路径经过的范围在[i ,k - 1]中,每次记录i - j 由哪个k更新,最后先加入最大值k在加入最小值i,通过递归确定j和i - j中的k
1.无向图正常的Floyd会存在自环,会改变dist,所以需要增加 i!=j 的条件就不存在一个点的环
2.给的边没有自环,当k = i or k = j 时 为INF,所以不会存在两个点的环
3. 2 * INF < int, 3 * INF > int ; 3个INF 会溢出,所以需要开long long
4. 输出的顺序需要注意i k j -> i,按照环的顺序输出即可
输入样例:
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20
输出样例:
1 3 5 2
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;
void get_path(int i, int j)
{
if (pos[i][j] == 0) return;
int k = pos[i][j];
get_path(i, k);
path[cnt ++ ] = k;
get_path(k, j);
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i ++ ) g[i][i] = 0;
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int res = INF;
memcpy(d, g, sizeof d);
for (int k = 1; k <= n; k ++ )
{
for (int i = 1; i < k; i ++ )
for (int j = i + 1; j < k; j ++ )
if ((long long)d[i][j] + g[j][k] + g[k][i] < res)
{
res = d[i][j] + g[j][k] + g[k][i];
cnt = 0;
path[cnt ++ ] = k;
path[cnt ++ ] = i;
get_path(i, j);
path[cnt ++ ] = j;
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
pos[i][j] = k;
}
}
if (res == INF) puts("No solution.");
else
{
for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
cout << endl;
}
return 0;
}
345. 牛站

输入样例:
2 6 6 4
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9
输出样例:
10
代码模板
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
const int N = 210;
int k, n, m, S, E;
int g[N][N];
int res[N][N];
void mul(int c[][N], int a[][N], int b[][N])
{
static int temp[N][N];
memset(temp, 0x3f, sizeof temp);
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
memcpy(c, temp, sizeof temp);
}
void qmi()
{
memset(res, 0x3f, sizeof res);
for (int i = 1; i <= n; i ++ ) res[i][i] = 0;
while (k)
{
if (k & 1) mul(res, res, g); // res = res * g
mul(g, g, g); // g = g * g
k >>= 1;
}
}
int main()
{
cin >> k >> m >> S >> E;
memset(g, 0x3f, sizeof g);
map<int, int> ids;
if (!ids.count(S)) ids[S] = ++ n;
if (!ids.count(E)) ids[E] = ++ n;
S = ids[S], E = ids[E];
while (m -- )
{
int a, b, c;
cin >> c >> a >> b;
if (!ids.count(a)) ids[a] = ++ n;
if (!ids.count(b)) ids[b] = ++ n;
a = ids[a], b = ids[b];
g[a][b] = g[b][a] = min(g[a][b], c);
}
qmi();
cout << res[S][E] << endl;
return 0;
}

浙公网安备 33010602011771号