单源最短路 以某个点为起点 d[]存到达其余各个点的所需最短距离
注意n个点 和 m个边的 范围不一样 使用需要注意链式前向星需要注意越界
```**------------恢复内容开始------------**
## dijkstra /spfa /floyd多源变单源
###热浪 https://www.acwing.com/problem/content/1131/
模板题 使用spfa过
spfa 从队列中取出点进行松弛操作 使用st[]记录点是否还在队列中 如果这个点本来就存在队列中那么就重复加入点了
void spfa(int s){
memset(d, 0x3f, sizeof d);
d[s]=0;
int hh=0,tt=1;
st[s]=true;q[0]=s;
while(hh!=tt){
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for (int i = h[t]; ~i ; i =ne[i] ){
int j=e[i];
if(d[j]>d[t]+w[i]){
d[j]=d[t]+w[i];
if(st[j]==false){
st[j]=true;
q[tt++]=j;
if(tt==N) tt=0;
}
}
}
}
}
##信使https://www.acwing.com/problem/content/1130/
广播式求时间 求广播所有点的最短时间 每个点接受到向他的邻边广播
指挥部到每个边的路径
相当于求一个点到所有点的最短路的最长长度
这里数据范围小使用Floyd
注意使用Floyd 必须初始化 d[i][i]=0;
##香甜的黄油 https://www.acwing.com/problem/content/1129/
求每个点的单源最短路->多源汇最短路
找出使得所有牛到达的路程之和 最短的牧场
单源最短路(spfa jijk堆)*n个点
注意: 这里既有没头牛n ,每个牧场p 还有通道c 需要分清之间的关系
对于距离是 每头牛到源点牧场的距离 所以可能重每个牧场的计算距离
int res = INF;
for (int i = 1; i <= p; i ++ ) res = min(res, spfa(i));
int spfa(){
int res=0;
for(int i=1;i<=n;++i){
int j=id[i];
if(d[j]==inf) return inf;//说明这个点不能作为起点
res+=d[j];
}
return res;
}
##转账最小花费 https://www.acwing.com/problem/content/1128/
求a转账到b后 b获得的价值最大 a的提供最小价值
b获得的 =a给的 *(w1*w2*w3...wn)
让(w1*w2*w3...wn)最大
d[j]存放的是转化到这个点的时候 价格是原来的几倍
松弛变成:
dist[j] = max(dist[j], dist[t] * g[t][j]);
预处理 :
double z = (100.0 - c) / 100;//转化为汇率
g[a][b] = g[b][a] = max(g[a][b], z);//
void dijkstra()
{
dist[S] = 1;
for (int i = 1; i <= n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] < dist[j]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
dist[j] = max(dist[j], dist[t] * g[t][j]);
}
}
int main()
{
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
double z = (100.0 - c) / 100;
g[a][b] = g[b][a] = max(g[a][b], z);
}
cin >> S >> T;
dijkstra();
printf("%.8lf\n", 100 / dist[T]);
return 0;
}
最优乘车https://www.acwing.com/problem/content/922/
第1个巴士站走到第n个巴士站 问换乘次数最少的路线
给出巴士路线(双向)
include
include
include
include
include
using namespace std;
const int N = 510;
int q[N];
int n,m;
int stop[N];
int d[N];
int g[N][N];
void bfs(){
memset(d, 0x3f, sizeof d);
queue
q.push(1);
d[1]=0;
while ( q.size() ){
auto t=q.front();
q.pop();
for (int i = 1; i <= n; i ++ ){
if(g[t][i]==1&&d[i]>d[t]+1){
d[i]=d[t]+1;
q.push(i);
}
}
}
}
int main()
{
cin>>m>>n;
getchar();
while (m -- ){
string s;
int cnt=0,p;
getline(cin,s);
stringstream ss(s);
while (ss>>p ){ stop[cnt++]=p;
}
for (int i = 0; i < cnt; i ++ ){
for (int j =i+1; j < cnt; j ++ ){
g[stop[i]][stop[j]]=1;
}
}
}
bfs();
if(d[n]==0x3f3f3f3f) cout <<"NO";
else cout <<max(d[n]-1,0) ;
return 0;
}
##昂贵的聘礼https://www.acwing.com/video/514/
优惠券问题
思路:
建立一个超级源点0,从0建立一条边到每个物品,权值为物品的价值。代表花费多少钱就可以购买这个物品。
若某个物品拥有替代品,代表从替代品建立一条边到这个物品,价值为替代的价值。 代表我有了这个替代品,那么还需要花费多少就能买这个物品。

最后就是等级制度。题目说的较高较低是按照等级差距说的 我们可以枚举每个等级区间,每次求最短路是只能更新在这个区间里面的物品。枚举所有情况求一个最小值就可以了。 特别注意的是区间必须包含1点。 那么范围就是【L[1] - m, L[1]】
include
include
include
include
using namespace std;
const int N = 105, M = 1e4 + 5, INF = 0x3f3f3f3f;
struct E {
int v, w, next;
} e[M];
int k, n, x, u, v, w, maxL = INF, len = 1, L[N], h[N], d[N];//L[i]代表i的等级
bool vis[N];
void add(int u, int v, int w) {
e[len].v = v;
e[len].w = w;
e[len].next = h[u];
h[u] = len++;
}
void spfa(int l, int r) {
memset(d, 0x3f, sizeof(d));
d[0] = 0;
queue
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
int w = e[j].w + d[u];
if (l <= L[v] && L[v] <= r && d[v] > w) {
d[v] = w;//看看这个到达的点对应等级,如果满足就变动价格
if (!vis[v]) vis[v] = true, q.push(v);
}
}
}
}
int main() {
scanf("%d%d", &k, &n);
for (int u = 1; u <= n; u++) {//u是每个点的编号 l的值是对应的等级
scanf("%d%d%d", &w, &L[u], &x);
maxL = max(maxL, L[u]);
add(0, u, w);//0号点花费w的价值就可以买
while (x--) {
scanf("%d%d", &v, &w);
add(v, u, w); //v-->u 有了v花费w就可以买u
}
}
//枚举下等级范围 求出最小的ans
int ans = INF;
for (int i = L[1] - k; i <= L[1]; i++) { //等级范围 肯定是要包含1点的 不然你连1都无法购买 等级只能从比1小的等级开始
//可以和[i, i + k]区间的人交易
spfa(i, i + k);
ans = min(d[1], ans);
}
printf("%d", ans);
return 0;
}
##新年好 DFS+最短路
需要知道起始点走遍图里多个点() 的最短距离
先求每个需要到的点作为源点的最短路 然后在爆搜求全排列 求最小值
include
include
include
include
include
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, 4*N);//这里不能是sizeof dist 否则把前面的所有dist都初始化了
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)//u是多少个点了 start是开始的点 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]);//dist[]表示的以谁为起点
memset(st, 0, sizeof st);
printf("%d\n", dfs(1, 0, 0));
return 0;
}
##二分找答案的题型
需要多一个数点10^6+1 用作当成无解的情形 用来分开有解和无解的情况
###通信线路https://www.acwing.com/problem/content/342/
###分层+双端队列广搜(就是deque两端插入 只获得左边的那个)
二分 如果大于x的变数小于k那么答案成功
将所有边分类 如果边长大于1,看做边权为1,否则取为0;
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);
}
}
所以有多少边 可以尝试用最短路
双端队列
include
include
include
include
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
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;//不超过的话边权设置为1
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;//如果满足不超过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;
}
##道路与航线
有正边又有负权 但是卡spfa
对于单向无环图DAG求最短路 需要使用top排序开始更新 可以保证更新的时候是最短的 由于spfa
top(dfs找连通块)+dijikstra堆优化
每个大点的入度
https://cdn.acwing.com/media/article/image/2020/04/09/27426_10ae25cc7a-1.png
include
include
include
using namespace std;
typedef pair<int, int> pii;
const int N = 25010, M = 200010, inf = 0x3f3f3f3f;
int n, mr, ms, s;
int h[N], w[M], e[M], ne[M], idx;
int id[N];
int cnt;
int dis[N];
bool st[N];
queue
vector
int d[N]; // 连通块的入度数组
void add (int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dij_heap(int u)//传入的是联通块
{
priority_queue<pii, vector<pii>, greater<pii>> pq;
for(int i = 0 ; i < block[u].size() ; i ++ )//对这个联通块所有点 放进他对应的点和距离
pq.push({dis[block[u][i]], block[u][i]});
while(pq.size())
{
auto t = pq.top();
pq.pop();
int x = t.first, y = t.second;
if(st[y]) continue;
st[y] = 1;
for(int i = h[y] ; i != -1 ; i = ne[i])
{
int j = e[i];//这里选择边的时候可能选择到航线 也就是负权图
if(dis[j] > x + w[i])
{
dis[j] = x + w[i];
if(id[j] == id[y]) pq.push({dis[j], j}); // 同一连通块才放进优先队列,防止了出现负权边, dijkstra无法解决
//负权边可以更新 但我们不放入堆里面 只处理一次
}
if (id[j] != id[y] && -- d[id[j]] == 0) q.push(id[j]); //如果 操作的是一条航线 因为是单向边 而且这次dij更新了点下次就不会选上了 所以只要操作了 那么入度就要减少了
}
}
}
void top_sort()
{
// memset整张图就将其串成了一张单源最短路的图
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;//取出来队头
for(int i = 1 ; i <= cnt ; i ++ )//开始
if(!d[i])
q.push(i);//如果入度为0 放进队列
while(q.size())
{
int t = q.front();
q.pop();
dij_heap(t);//取出 队头做dijistra
}
}
void dfs (int u, int v) // flood-fill标记每个点所属的连通分量的编号
{
id[u] = v;//u在连通块u里面
block[v].push_back(u);//第v个联通块里面有点u
for(int i = h[u] ; i != -1 ; i = ne[i])
{
int j = e[i];
if(!id[j]) dfs(j, v);//只有没有编号的时候才需要dfs
}
}
int main ()
{
scanf("%d%d%d%d", &n, &mr, &ms, &s);
memset(h, -1, sizeof h);
for(int i = 0 ; i < mr ; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);//道路读进来
add(a, b, c);
add(b, a, c);
}
for(int i = 1 ; i <= n ; i ++ )//floor fill求连通块
if(!id[i])//没有编号
dfs(i, ++ cnt);//从i开始做 cnt表示编号
for(int i = 0 ; i < ms ; i ++ )//航线读进来
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
d[id[b]] ++ ;//b所在的联通块入度+1
}
top_sort();//从连通块为0开始做一定最简单
for(int i = 1 ; i <= n ; i ++ )
if(dis[i] > inf / 2) // 见分享带负权图判断最短路径是否存在注意事项
puts("NO PATH");
else printf("%d\n", dis[i]);
return 0;
}
##最优贸易https://www.acwing.com/solution/content/3709/
从1号点开始出发 选择一个点买入 选择一个点卖出
从 1 走到 i 的过程中,买入水晶球的最低价格 dmin[i];
从 i 走到 n 的过程中,卖出水晶球的最高价格 dmax[i];
然后枚举每一个点求出 dmax[i]-dmin[i]的最大值
这里的松弛操作和以往的不同 只是点上面的 只是选择操作 不需要+操作
求dmin[]的时候 由于不是拓扑图 不能dp 又可能一位内dmin[i]可能被更新多次 所以可能不适用于dij堆
include
include
include
include
include
using namespace std;
const int N = 100010, M = 2000010;
int n, m;
int price[N];
int h[N], rh[N], e[M], ne[M], idx;//rh是反向点
int dmin[N], dmax[N];//dmin求最小值
bool st[N];
void add(int *h, int a, int b)//add第一个参数是传入正图 还是返图
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa(int *d, int start, int *h, bool flag)
{
queue
memset(st, 0, sizeof st);
if (flag) memset(d, 0x3f, sizeof dmin);//flag=ture 表示求最小值 需要把距离设置位最大值
q.push(start);
st[start] = true;
d[start] = price[start];
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 (flag && d[j] > min(d[t], price[j]) || !flag && d[j] < max(d[t], price[j]))//判断类型并且更新
{
if (flag) d[j] = min(d[t], price[j]);
else d[j] = max(d[t], price[j]);
if (!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 1; i <= n; i ++ ) scanf("%d", &price[i]);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(h, a, b), add(rh, b, a);
if (c == 2) add(h, b, a), add(rh, a, b);
}
spfa(dmin, 1, h, true);//正向找最小值
spfa(dmax, n, rh, false);//反向找最大值
////因为最大值i之后的点到n的最大值,所以不如直接从n开始找最大值 是从n开始(从i到n可转化为从n到i),所以我们把所有路径倒个头,建图的时候正向图是a->b 但是反向图的时候变成b->a 从最最小值最大值还是这么求
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, dmax[i] - dmin[i]);
printf("%d\n", res);
return 0;
}
**------------恢复内容开始------------**
**------------恢复内容开始------------**
注意n个点 和 m个边的 范围不一样 使用需要注意链式前向星需要注意越界
## dijkstra /spfa /floyd多源变单源
###热浪 https://www.acwing.com/problem/content/1131/
模板题 使用spfa过
spfa 从队列中取出点进行松弛操作 使用st[]记录点是否还在队列中 如果这个点本来就存在队列中那么就重复加入点了
void spfa(int s){
memset(d, 0x3f, sizeof d);
d[s]=0;
int hh=0,tt=1;
st[s]=true;q[0]=s;
while(hh!=tt){
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for (int i = h[t]; ~i ; i =ne[i] ){
int j=e[i];
if(d[j]>d[t]+w[i]){
d[j]=d[t]+w[i];
if(st[j]==false){
st[j]=true;
q[tt++]=j;
if(tt==N) tt=0;
}
}
}
}
}
##信使https://www.acwing.com/problem/content/1130/
广播式求时间 求广播所有点的最短时间 每个点接受到向他的邻边广播
指挥部到每个边的路径
相当于求一个点到所有点的最短路的最长长度
这里数据范围小使用Floyd
注意使用Floyd 必须初始化 d[i][i]=0;
##香甜的黄油 https://www.acwing.com/problem/content/1129/
求每个点的单源最短路->多源汇最短路
找出使得所有牛到达的路程之和 最短的牧场
单源最短路(spfa jijk堆)*n个点
注意: 这里既有没头牛n ,每个牧场p 还有通道c 需要分清之间的关系
对于距离是 每头牛到源点牧场的距离 所以可能重每个牧场的计算距离
int res = INF;
for (int i = 1; i <= p; i ++ ) res = min(res, spfa(i));
int spfa(){
int res=0;
for(int i=1;i<=n;++i){
int j=id[i];
if(d[j]==inf) return inf;//说明这个点不能作为起点
res+=d[j];
}
return res;
}
##转账最小花费 https://www.acwing.com/problem/content/1128/
求a转账到b后 b获得的价值最大 a的提供最小价值
b获得的 =a给的 *(w1*w2*w3...wn)
让(w1*w2*w3...wn)最大
d[j]存放的是转化到这个点的时候 价格是原来的几倍
松弛变成:
dist[j] = max(dist[j], dist[t] * g[t][j]);
预处理 :
double z = (100.0 - c) / 100;//转化为汇率
g[a][b] = g[b][a] = max(g[a][b], z);//
void dijkstra()
{
dist[S] = 1;
for (int i = 1; i <= n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] < dist[j]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
dist[j] = max(dist[j], dist[t] * g[t][j]);
}
}
int main()
{
scanf("%d%d", &n, &m);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
double z = (100.0 - c) / 100;
g[a][b] = g[b][a] = max(g[a][b], z);
}
cin >> S >> T;
dijkstra();
printf("%.8lf\n", 100 / dist[T]);
return 0;
}
最优乘车https://www.acwing.com/problem/content/922/
第1个巴士站走到第n个巴士站 问换乘次数最少的路线
给出巴士路线(双向)
include
include
include
include
include
using namespace std;
const int N = 510;
int q[N];
int n,m;
int stop[N];
int d[N];
int g[N][N];
void bfs(){
memset(d, 0x3f, sizeof d);
queue
q.push(1);
d[1]=0;
while ( q.size() ){
auto t=q.front();
q.pop();
for (int i = 1; i <= n; i ++ ){
if(g[t][i]==1&&d[i]>d[t]+1){
d[i]=d[t]+1;
q.push(i);
}
}
}
}
int main()
{
cin>>m>>n;
getchar();
while (m -- ){
string s;
int cnt=0,p;
getline(cin,s);
stringstream ss(s);
while (ss>>p ){ stop[cnt++]=p;
}
for (int i = 0; i < cnt; i ++ ){
for (int j =i+1; j < cnt; j ++ ){
g[stop[i]][stop[j]]=1;
}
}
}
bfs();
if(d[n]==0x3f3f3f3f) cout <<"NO";
else cout <<max(d[n]-1,0) ;
return 0;
}
##昂贵的聘礼https://www.acwing.com/video/514/
优惠券问题
思路:
建立一个超级源点0,从0建立一条边到每个物品,权值为物品的价值。代表花费多少钱就可以购买这个物品。
若某个物品拥有替代品,代表从替代品建立一条边到这个物品,价值为替代的价值。 代表我有了这个替代品,那么还需要花费多少就能买这个物品。

最后就是等级制度。题目说的较高较低是按照等级差距说的 我们可以枚举每个等级区间,每次求最短路是只能更新在这个区间里面的物品。枚举所有情况求一个最小值就可以了。 特别注意的是区间必须包含1点。 那么范围就是【L[1] - m, L[1]】
include
include
include
include
using namespace std;
const int N = 105, M = 1e4 + 5, INF = 0x3f3f3f3f;
struct E {
int v, w, next;
} e[M];
int k, n, x, u, v, w, maxL = INF, len = 1, L[N], h[N], d[N];//L[i]代表i的等级
bool vis[N];
void add(int u, int v, int w) {
e[len].v = v;
e[len].w = w;
e[len].next = h[u];
h[u] = len++;
}
void spfa(int l, int r) {
memset(d, 0x3f, sizeof(d));
d[0] = 0;
queue
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
int w = e[j].w + d[u];
if (l <= L[v] && L[v] <= r && d[v] > w) {
d[v] = w;//看看这个到达的点对应等级,如果满足就变动价格
if (!vis[v]) vis[v] = true, q.push(v);
}
}
}
}
int main() {
scanf("%d%d", &k, &n);
for (int u = 1; u <= n; u++) {//u是每个点的编号 l的值是对应的等级
scanf("%d%d%d", &w, &L[u], &x);
maxL = max(maxL, L[u]);
add(0, u, w);//0号点花费w的价值就可以买
while (x--) {
scanf("%d%d", &v, &w);
add(v, u, w); //v-->u 有了v花费w就可以买u
}
}
//枚举下等级范围 求出最小的ans
int ans = INF;
for (int i = L[1] - k; i <= L[1]; i++) { //等级范围 肯定是要包含1点的 不然你连1都无法购买 等级只能从比1小的等级开始
//可以和[i, i + k]区间的人交易
spfa(i, i + k);
ans = min(d[1], ans);
}
printf("%d", ans);
return 0;
}
##新年好 DFS+最短路
需要知道起始点走遍图里多个点() 的最短距离
先求每个需要到的点作为源点的最短路 然后在爆搜求全排列 求最小值
include
include
include
include
include
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, 4*N);//这里不能是sizeof dist 否则把前面的所有dist都初始化了
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)//u是多少个点了 start是开始的点 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]);//dist[]表示的以谁为起点
memset(st, 0, sizeof st);
printf("%d\n", dfs(1, 0, 0));
return 0;
}
##二分找答案的题型
需要多一个数点10^6+1 用作当成无解的情形 用来分开有解和无解的情况
###通信线路https://www.acwing.com/problem/content/342/
###分层+双端队列广搜(就是deque两端插入 只获得左边的那个)
二分 如果大于x的变数小于k那么答案成功
将所有边分类 如果边长大于1,看做边权为1,否则取为0;
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);
}
}
所以有多少边 可以尝试用最短路
双端队列
include
include
include
include
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
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;//不超过的话边权设置为1
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;//如果满足不超过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;
}
##道路与航线
有正边又有负权 但是卡spfa
对于单向无环图DAG求最短路 需要使用top排序开始更新 可以保证更新的时候是最短的 由于spfa
top(dfs找连通块)+dijikstra堆优化
每个大点的入度
https://cdn.acwing.com/media/article/image/2020/04/09/27426_10ae25cc7a-1.png
include
include
include
using namespace std;
typedef pair<int, int> pii;
const int N = 25010, M = 200010, inf = 0x3f3f3f3f;
int n, mr, ms, s;
int h[N], w[M], e[M], ne[M], idx;
int id[N];
int cnt;
int dis[N];
bool st[N];
queue
vector
int d[N]; // 连通块的入度数组
void add (int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dij_heap(int u)//传入的是联通块
{
priority_queue<pii, vector<pii>, greater<pii>> pq;
for(int i = 0 ; i < block[u].size() ; i ++ )//对这个联通块所有点 放进他对应的点和距离
pq.push({dis[block[u][i]], block[u][i]});
while(pq.size())
{
auto t = pq.top();
pq.pop();
int x = t.first, y = t.second;
if(st[y]) continue;
st[y] = 1;
for(int i = h[y] ; i != -1 ; i = ne[i])
{
int j = e[i];//这里选择边的时候可能选择到航线 也就是负权图
if(dis[j] > x + w[i])
{
dis[j] = x + w[i];
if(id[j] == id[y]) pq.push({dis[j], j}); // 同一连通块才放进优先队列,防止了出现负权边, dijkstra无法解决
//负权边可以更新 但我们不放入堆里面 只处理一次
}
if (id[j] != id[y] && -- d[id[j]] == 0) q.push(id[j]); //如果 操作的是一条航线 因为是单向边 而且这次dij更新了点下次就不会选上了 所以只要操作了 那么入度就要减少了
}
}
}
void top_sort()
{
// memset整张图就将其串成了一张单源最短路的图
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;//取出来队头
for(int i = 1 ; i <= cnt ; i ++ )//开始
if(!d[i])
q.push(i);//如果入度为0 放进队列
while(q.size())
{
int t = q.front();
q.pop();
dij_heap(t);//取出 队头做dijistra
}
}
void dfs (int u, int v) // flood-fill标记每个点所属的连通分量的编号
{
id[u] = v;//u在连通块u里面
block[v].push_back(u);//第v个联通块里面有点u
for(int i = h[u] ; i != -1 ; i = ne[i])
{
int j = e[i];
if(!id[j]) dfs(j, v);//只有没有编号的时候才需要dfs
}
}
int main ()
{
scanf("%d%d%d%d", &n, &mr, &ms, &s);
memset(h, -1, sizeof h);
for(int i = 0 ; i < mr ; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);//道路读进来
add(a, b, c);
add(b, a, c);
}
for(int i = 1 ; i <= n ; i ++ )//floor fill求连通块
if(!id[i])//没有编号
dfs(i, ++ cnt);//从i开始做 cnt表示编号
for(int i = 0 ; i < ms ; i ++ )//航线读进来
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
d[id[b]] ++ ;//b所在的联通块入度+1
}
top_sort();//从连通块为0开始做一定最简单
for(int i = 1 ; i <= n ; i ++ )
if(dis[i] > inf / 2) // 见分享带负权图判断最短路径是否存在注意事项
puts("NO PATH");
else printf("%d\n", dis[i]);
return 0;
}
##最优贸易https://www.acwing.com/solution/content/3709/
从1号点开始出发 选择一个点买入 选择一个点卖出
从 1 走到 i 的过程中,买入水晶球的最低价格 dmin[i];
从 i 走到 n 的过程中,卖出水晶球的最高价格 dmax[i];
然后枚举每一个点求出 dmax[i]-dmin[i]的最大值
这里的松弛操作和以往的不同 只是点上面的 只是选择操作 不需要+操作
求dmin[]的时候 由于不是拓扑图 不能dp 又可能一位内dmin[i]可能被更新多次 所以可能不适用于dij堆
include
include
include
include
include
using namespace std;
const int N = 100010, M = 2000010;
int n, m;
int price[N];
int h[N], rh[N], e[M], ne[M], idx;//rh是反向点
int dmin[N], dmax[N];//dmin求最小值
bool st[N];
void add(int *h, int a, int b)//add第一个参数是传入正图 还是返图
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa(int *d, int start, int *h, bool flag)
{
queue
memset(st, 0, sizeof st);
if (flag) memset(d, 0x3f, sizeof dmin);//flag=ture 表示求最小值 需要把距离设置位最大值
q.push(start);
st[start] = true;
d[start] = price[start];
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 (flag && d[j] > min(d[t], price[j]) || !flag && d[j] < max(d[t], price[j]))//判断类型并且更新
{
if (flag) d[j] = min(d[t], price[j]);
else d[j] = max(d[t], price[j]);
if (!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 1; i <= n; i ++ ) scanf("%d", &price[i]);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(h, a, b), add(rh, b, a);
if (c == 2) add(h, b, a), add(rh, a, b);
}
spfa(dmin, 1, h, true);//正向找最小值
spfa(dmax, n, rh, false);//反向找最大值
////因为最大值i之后的点到n的最大值,所以不如直接从n开始找最大值 是从n开始(从i到n可转化为从n到i),所以我们把所有路径倒个头,建图的时候正向图是a->b 但是反向图的时候变成b->a 从最最小值最大值还是这么求
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, dmax[i] - dmin[i]);
printf("%d\n", res);
return 0;
}
##拯救大兵瑞恩https://www.acwing.com/activity/content/problem/content/1504/
从左上角走到右下角 有墙和钥匙和门的条件
地图+双端队列+状态
1.给所有点 一个编号
2.插入边 记得用过的放入set表示不能通过 如果c为0就不插入了。
然后再bulid里面上下左右四个方向插入边
3.输入钥匙 key数组下表存编号 值为获得的钥匙
4.双端队列PII 进行bfs 每次走的格子有的是钥匙 就更新状态 并且放到队头
如果走的格子没有钥匙就更新步数放到队尾
include
include
include
include
include
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
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 ++ )//4个方向建边
{
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];//a为原来的点 b为之后的点
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;//类似于dijkstra
if (t.x == n * m) return dist[t.x][t.y];//已经等于最后一个点 就相等
//有钥匙就先更新自己状态
if (key[t.x])//key对应的下标是 位置 存的值表示第几个状态的点
{
int state = t.y | key[t.x];
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];
//w[i]存储了拥有的点
if (w[i] && !(t.y >> (w[i] - 1) & 1)) continue; // 有门但没有钥匙 w[i]-1 表示钥匙的编号
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});//边权是1加到队尾
}
}
}
return -1;
}
int main()
{
cin >> n >> m >> p >> k;//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);//c-1表示编号减少一枚状态 key数组下标是对应的点 如果是0就是没钥匙 如果有输就是表示对应的钥匙
//用|=因为不只是一个钥匙
}
cout << bfs() << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具