1.2 最短路算法的多用

6 篇文章 0 订阅
4 篇文章 0 订阅

1.2.1 分算法之Dijkstra

算法本质是贪心,一些不像图论的可贪心的让求关于单源的解的题也能用哦。不能解决负权边。
Eg1.佳佳的魔法药水
    一道看起来和最短路没啥关系的题,但是可以贪心啊,每次找一个值最小但却没有确定最小值的药水,将其标记为最小值,然后枚举能与此药水合成药水的药水,用找到的药水与配对的药水更新合成药水的最小值,和Dij相似。
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1010;
const int inf = 0x3f3f3f3f;

int n, p[maxn], ans[maxn], now, mp[maxn][maxn];
bool vis[maxn];

void Dijkstra(){
    for(int i = 1; i < n; i++){
        int low = inf;
        for(int j = 1; j <= n; j++)
            if(!vis[j] && p[j] < low){
                low = p[j];
                now = j;
            }
        vis[now] = 1;
        for(int j = 1; j <= n; j++)
            if(vis[j] && mp[now][j]){//Dij要找已松弛的!!!! 
                if(p[mp[now][j]] > p[now] + p[j]){
                    ans[mp[now][j]] = ans[now] * ans[j];
                    p[mp[now][j]] = p[now] + p[j];
                }
                else if(p[mp[now][j]] == p[now] + p[j])
                    ans[mp[now][j]] += ans[now] * ans[j];
            }
    }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &p[i]);//表示初始都是靠买哒 
        ans[i] = 1;
    }
    int x, y, z;
    while(scanf("%d%d%d", &x, &y, &z) != EOF)
        mp[x+1][y+1] = mp[y+1][x+1] = z + 1;
    Dijkstra();
    printf("%d %d\n", p[1], ans[1]);
    return 0;
}
Eg2.板子
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1010;
const int inf = 0x3f3f3f3f;

struct data{
    int v, w, nxt;
} edge[maxn*maxn*2];

struct node{
    int point, w;
    bool operator < (const node& a) const {
        return w < a.w;
    }
};

int dis[maxn], vis[maxn], st[maxn];
int n, m, tot;

inline void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void Dijkstra(){
    priority_queue<node> q;
    for(int i = 0; i <= n; i++) dis[i] = inf;
    q.push((node){1, 0});
    dis[1] = 0;
    while(!q.empty()){
        node now = q.top(); q.pop();
        if(vis[now.point])  continue;
        vis[now.point] = 1;
        for(int i = st[now.point]; i; i = edge[i].nxt){
            int to = edge[i].v;
            if(dis[to] > dis[now.point] + edge[i].w){
                dis[to] = dis[now.point] + edge[i].w;
                q.push((node){to, dis[to]});
            }
        }
    }
    printf("%d\n", dis[n]);
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, x, y, z; i <= m; i++){
        scanf("%d%d%d", &x, &y, &z);
        in(x, y, z);
        in(y, x, z); 
    }
    Dijkstra();
    return 0;
}

1.2.2 SPFA

自用最短路板子!!!可求瓶颈路!不太稳啊。。希望不被卡。。
Eg1.营救
    瓶颈路!!!就是让路上的最大值尽量小!!!把加号改成max即可!
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010; 
int n, m, s, t, tot;
int d[maxn], st[maxn];
bool vis[maxn];
queue<int> q;
struct node{
    int v, w, nxt;
} edge[2*maxn];

inline void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void spfa(){
    memset(d, 0x3f3f3f3f, sizeof(d));
    q.push(s);
    vis[s] = 1;
    d[s] = 0;
    while(!q.empty()){
        int now = q.front(); q.pop();
        vis[now] = 0;
        for(int i = st[now]; i; i = edge[i].nxt){
            int to = edge[i].v;
            if(max(d[now], edge[i].w) < d[to]){
                d[to] = max(d[now], edge[i].w);
                vis[to] = 1;
                q.push(to);
            }
        }
    }
}

int main(){
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for(int i = 1, x, y, z; i <= m; i++){
        scanf("%d%d%d", &x, &y, &z);
        in(x, y, z);
        in(y, x, z);
    }
    spfa();
    printf("%d\n", d[t]);
    return 0;
} 
Eg2.玛丽卡
    求出最短路然后枚举最短路上的边一一删除,关键在于记录路径的方式,path[to] = i即可,然后用链表来遍历!
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
int tot, n, m, cnt;
struct node{
    int u, v, w, next;
} edge[maxn*maxn];
int st[maxn], d[maxn], fe[maxn*maxn];
bool v[maxn];

void in(int a, int b, int c){
    edge[++tot].u = a;
    edge[tot].v = b;
    edge[tot].w = c;
    edge[tot].next = st[a];
    st[a] = tot;
}

void init(){
    memset(v, 0, sizeof(v));
    for(int i = 0; i <= n; i++)
        d[i] = inf;
}

void spfa(int begin, int end, int judge){
    queue<int> q;
    init();
    d[begin] = 0;
    v[begin] = 1;
    q.push(begin);
    while(!q.empty()){
        int from = q.front(); q.pop();
        v[from] = 0;
        for(int i = st[from]; i > 0; i = edge[i].next){
            int to = edge[i].v;
            if(d[to] > d[from] + edge[i].w){
                d[to] = d[from] + edge[i].w;
                if(judge)
                        fe[to] = i;
                if(!v[to]){
                    v[to] = 1;
                    q.push(to);
                }
            }
        }
    }
}

int main(){
    cin >> n >> m;
    for(int j = 1, a, b, c; j <= m; j++){
        cin >> a >> b >> c;
        in(a, b, c);
        in(b, a, c);
    }
    memset(fe, -1, sizeof(fe));
    spfa(n, 1, 1);
    int ans = d[1];
    //cout << ans << endl;
    for(int i = fe[1]; i > 0; i = fe[edge[i].u]){
        //cout << edge[i].v << " ";
        int t = edge[i].w;
        //cout << t << endl;
        edge[i].w = inf;
        spfa(n, 1, 0);
        //cout << d[1] << endl;
        edge[i].w = t;
        ans = max(ans, d[1]);
    }
    cout << ans << endl;
    return 0;
}
Eg3.小K的农场
    差分约束系统,a-b >= c 则建一条b到a权值为c的边,a = b则建权值为0的双向边,然后跑SPFA的**DFS版**判负环,这样效率为O(n)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
typedef long long Lovelive;
using namespace std;
inline void scan(int &x) {
    int f = 1; x = 0;char c = getchar();
    while(c > '9' || c < '0') {if(c == '-') f= -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x*10 + c - '0', c = getchar();
    f*x;
}
const int maxn=2e4+10;
int n, m;
struct Edge {
    int v, next, val;
}e[maxn];
int last[maxn], tot;
void add(int u, int v, int w) {
    e[++tot].next=last[u]; e[tot].v=v; e[tot].val=w; last[u]=tot;
}
int dis[maxn];
bool vis[maxn]; 
bool SPFA(int u) {
    vis[u]=1;
    for(int i=last[u]; i; i=e[i].next) {
        if(dis[e[i].v] > dis[u]+e[i].val) {
            dis[e[i].v] = dis[u]+e[i].val;
            if(vis[e[i].v]) return false;
            else if(!SPFA(e[i].v)) return false;
        }
    }
    vis[u]=0;
    return true;
}
int main() {
    scan(n); scan(m);
    while(m--) {
        int k; scan(k);
        if(k == 3) { int a, b; scan(a), scan(b); add(b, a, 0); add(a, b, 0);}
        else {
            int a, b, c;
            scan(a); scan(b); scan(c);
            if(k==1) add(a, b, -c);
            else add(b, a, c);
        }
    }
    for(int i=0; i<=n; i++) add(0, i, 0), dis[i]=maxn;
    dis[0]=0;
    if(SPFA(0)) cout << "Yes";
    else cout << "No";
    return 0;
}
    Eg4.最小圈
    分数规划,同样用SPFA判负环,二分答案。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 3005, M = 1e4 + 5; const double eps = 1e-9;
int n, m, ecnt, nxt[M], adj[N], go[M], w[M]; double val[M], dis[N];
bool Flag, vis[N];
void add_edge(int u, int v, int x) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; w[ecnt] = x;
}
void dfs(int u) {
    vis[u] = 1;
    for (int e = adj[u], v; e; e = nxt[e])
        if (dis[u] + val[e] < dis[v = go[e]]) {
            if (vis[v]) return (void) (Flag = 1);
            else {
                dis[v] = dis[u] + val[e];
                dfs(v);
            }
        }
    vis[u] = 0;
}
bool check(double x) {
    int i; memset(vis, 0, sizeof(vis));
    for (i = 1; i <= ecnt; i++) val[i] = 1.0 * w[i] - x;
    memset(dis, 0, sizeof(dis));
    for (i = 1; i <= n; i++) {
        Flag = 0;
        if (dfs(i), Flag) return 1;
    }
    return 0;
}
double solve() {
    double l = -1e5, r = 1e5;
    while (fabs(r - l) > eps) {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}
int main() {
    int i, x, y, z; n = read(); m = read();
    for (i = 1; i <= m; i++) x = read(), y = read(),
        z = read(), add_edge(x, y, z);
    printf("%.8lf\n", solve());
    return 0;
}

1.2.3 Floyd
预处理多元会用到
Eg1.Vijos 观光旅游
判最小正环

    //设环的起点(终点)为i,那么环可以表示为i->k->j->x1,x2...(不经过k)->i
    //环的长度可以表示为i->j经过k的dist+i->不经过k的dist
    //i->j经过k的dist普通floyd就能求到,不经过k的dist需要构造
    //在求这个环时,保持k是最大值,那么所有x1,x2...都比k要小
    //循环遍历至k-1时存储的就是j->x1,x2...->i的最小值 
    for(int k=1;k<=n;k++){//k是用来更新的中间点,保持k比i,j都要大, 
        for(int i=1;i<k;i++)//floyd每次更新从i开始到j结束,中间经过k的距离
            for(int j=i+1;j<k;j++){
                ans=min(ans,dist[i][j]+edge[i][k]+edge[k][j]);
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
    } 
    if(ans<INF) printf("%d\n",ans);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值