单源最短路 以某个点为起点 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);
queueq;
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建立一条边到每个物品,权值为物品的价值。代表花费多少钱就可以购买这个物品。
若某个物品拥有替代品,代表从替代品建立一条边到这个物品,价值为替代的价值。 代表我有了这个替代品,那么还需要花费多少就能买这个物品。
![](https://img2022.cnblogs.com/blog/2525875/202204/2525875-20220423221441327-1024365667.png)

最后就是等级制度。题目说的较高较低是按照等级差距说的 我们可以枚举每个等级区间,每次求最短路是只能更新在这个区间里面的物品。枚举所有情况求一个最小值就可以了。 特别注意的是区间必须包含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;
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,否则取为0for (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 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;//不超过的话边权设置为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 q;//这里面是连通块的编号 处理top排序的
vector block[N]; // 连通块数组
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 q;
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);
queueq;
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建立一条边到每个物品,权值为物品的价值。代表花费多少钱就可以购买这个物品。
若某个物品拥有替代品,代表从替代品建立一条边到这个物品,价值为替代的价值。 代表我有了这个替代品,那么还需要花费多少就能买这个物品。
![](https://img2022.cnblogs.com/blog/2525875/202204/2525875-20220423221441327-1024365667.png)

最后就是等级制度。题目说的较高较低是按照等级差距说的 我们可以枚举每个等级区间,每次求最短路是只能更新在这个区间里面的物品。枚举所有情况求一个最小值就可以了。 特别注意的是区间必须包含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;
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,否则取为0for (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 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;//不超过的话边权设置为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 q;//这里面是连通块的编号 处理top排序的
vector block[N]; // 连通块数组
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 q;
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 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 ++ )//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;

}

posted @   liang302  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
主题色彩