最短路学习小结

To be continued…

最短路算法

dijkstra算法

适用范围:无负权边
时间复杂度: O(N2) O ( N 2 ) (无优化时)
然而…优先队列优化的dijkstra的均摊时间复杂度是多少?
斐波那契堆优化的dijkstra的复杂度(大约)是 O(MlogN) O ( M l o g N ) ,尽管优先队列优化没有堆优化那么强,但姑且当做这么多吧
无优化的dijkstra:(适用于稠密图)

void dijkstra(int s){
    int x=1;
    vis[s]=1;
    while(x<n){
        int mi=1<<30,xi;
        for(int i=1;i<=n;i++)
            if(vis[i])
                for(int j=head[i];j;j=edge[i].nxt){
                    int v=edge[i].v,w=edge[i].w;
                    if(!vis[v]){
                        if(d[v]>d[u]+w){
                            d[v]=d[u]+w;
                            pre[v]=u;//输出路径用
                            if(d[v]<mi)
                                mi=d[v],xi=v;
                        }
                    }
                }
        vis[xi]=1;
        x++;
    }
}

优先队列优化的dijkstra:适用于稀疏图

void dijkstra(int s){
    q.push(make_pair(0,s));
    while(!q.empty()){
        int u=q.front().first,val=q.front().second;
        q.pop();
        if(val>dist[u])
            continue ;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            if(dist[v]>dist[u]+w){
                dist[v]=dist[u]+w;
                q.push(make_pair(dist[v],v));
            }
        }
    }
}

SPFA算法

使用范围:基本所有
时间复杂度证明极其有毒,姑且看作跟优先队列优化的dijkstra一个数量级吧

void SPFA(int s){
    q.push(s);
    inqueue[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inqueue[u]=0;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            if(dist[v]>dist[u]+dist[w]){
                dist[v]=dist[u]+dist[w];
                if(!inqueue[v]){
                    inqueue[v]=1;
                    q.push(v);
                }
            }
        }
    }
}

floyd算法

时间复杂度 O(N3) O ( N 3 )
那就直接上一个最经典的5行式代码吧
储存请用邻接矩阵

void floyd(){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dist[i][k]+dist[k][j]<dist[i][j])
                    dist[i][j]=dist[i][k]+dist[k][j];
}

例题

例题1 hdu 2544 最短路裸题

题解

代码?上面不是一堆吗

例题2 POJ3259 Wormholes

给出一个图,让你判断是否存在负权环

题解

我们可以在SPFA的时候记录每个点入队次数
如果超过了N,则有负权环

为什么在SPFA算法中,判断负权回路的条件是任一节点进队次数超过总结点数? - 好地方bug的回答 - 知乎
https://www.zhihu.com/question/64299526/answer/221033086

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1000,M=100000;
struct node{
    int u,v,next;
    double w;
}edge[M];
int head[N],cnt;
int dist[N];
bool inqueue[N];
int intime[N];
queue<int> q;
void add_edge(int u,int v,double w){
    ++cnt;
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
bool SPFA(int s,int n){
    memset(dist,0x3f,sizeof dist);
    memset(inqueue,0,sizeof inqueue);
    memset(intime,0,sizeof intime);
    q.push(s);
    dist[s]=0;
    inqueue[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inqueue[u]=0;
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].v;
            if(dist[v]>dist[u]+edge[i].w){
                dist[v]=dist[u]+edge[i].w;
                if(!inqueue[v]){
                    inqueue[v]=1;
                    q.push(v);
                    intime[v]++;
                    if(intime[v]>n){
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m1,m2;
        scanf("%d%d%d",&n,&m1,&m2);
        memset(head,0,sizeof head);
        cnt=0;
        for(int i=1;i<=m1;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
            add_edge(v,u,w);
        }
        for(int i=1;i<=m2;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,-w);
        }
        bool k=1;
        for(int i=1;i<=n&&k;i++){
            if(SPFA(i,n)){
                printf("YES\n");
                k=0;
            }
        }
        if(k)
            printf("NO\n");
    }
}

例题3 POJ 3660 Cow Contest

N(N<=100)头奶牛,给出以下形如此的关系
A B 表示A的实力强于B
求有多少奶牛的名次能确定。数据保证不会自相矛盾

题解

连边,邻接矩阵这次我们开成bool数组,然后连边,跑floyd,如果某个点入度+出度=N-1,则该点可以确定排名,ans++


差分约束系统

概述

n个变量和m个不等式,形如 xixj<=ak x i − x j <= a k 求某一对 xtxs x t − x s 的最大值
那么也就是说, xi<=xj+ak x i <= x j + a k
也就可以看成 disti<=distj+w(j,i) d i s t i <= d i s t j + w ( j , i )
所以以s为起点,跑一遍最短路, distt d i s t t 即为所求
特殊情况:
1.存在负环:无解
2.无法到达t: xtxs x t − x s 可以无限大
注意:此处的不等号必须带等号,否则需要转化
如果需要求的是最小值,可以将所有不等式转化为>=,然后跑最长路(此时出现正环为无解)

dfs版SPFA

判负环原理:如果在递归的松弛中,如果松弛了一个还在栈中的节点,则该点一定在负环上

void SPFA(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        double w=edge[i].w;
        if(dist[u]+w<dist[v]){
            if(vis[v]){
                fuquanhuan=true;//不能直接return,要清标记
                break ;
            }
            else{
                dist[v]=dist[u]+w;
                SPFA(v);
            }
        }
    }
    vis[u]=0;
}

例题4 bzoj2330 糖果

输入的第一行是两个整数N,K。
接下来K行,表示这些点需要满足的关系,每行3个数字,X,A,B。
如果X=1, 表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多;
如果X=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果;
如果X=3, 表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果;
如果X=4, 表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果;
如果X=5, 表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果;
输出一行,表示lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出-1。

题解

标准的差分约束大版题
应该跑最长路
图例:u->v:w表示u到v连一条权值为w的边
建边:
1:a->b:0 b->a:0
2:a->b:1
3:b->a:0
4:b->a:1
5:a->b:0
额…为什么我首先想到小于是<然后看到不小于就以为是<=…

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define MAXN 100000
using namespace std;
typedef long long LL;
struct edge
{
    int to,val;
    edge(){};
    edge(int _to,int _val):to(_to),val(_val){}
};
vector<edge> G[MAXN+5];
int dist[MAXN+5];
int times[MAXN+5],n;
bool inque[MAXN+5],flag=true;
queue<int> que;
void SPFA()
{
    for(int i=1;i<=n;i++)
        dist[i]=1,times[i]=1,inque[i]=true,que.push(i);
    while(que.empty()==false)
    {
        int u=que.front();
        que.pop();

        inque[u]=false;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i].to;
            if(dist[v]<dist[u]+G[u][i].val)
            {
                dist[v]=dist[u]+G[u][i].val;
                if(inque[v]==false){
                        que.push(v),inque[v]=true,times[v]++;
                        if(times[u]>n){
                            flag=false;
                            return;
                        }
                }
            }
        }
    }
}
int main()
{
    int k,x,a,b;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=k;i++)
    {
        scanf("%d %d %d",&x,&a,&b);
        if(x==1)//a==b
        {
            G[a].push_back(edge(b,0));
            G[b].push_back(edge(a,0));
        }
        else if(x==2)//a<b->b>=a+1
            G[a].push_back(edge(b,1));
        else if(x==3)//a>=b
            G[b].push_back(edge(a,0));
        else if(x==4)//a>b->a>=b+1
            G[b].push_back(edge(a,1));
        else if(x==5)//b>=a
            G[a].push_back(edge(b,0));
    }
    SPFA();
    if(flag==false)
    {
        printf("-1\n");
        return 0;
    }
    LL tot=0;
    for(int i=1;i<=n;i++)
        tot+=1LL*dist[i];
    printf("%lld\n",tot);
    return 0;
}

例题5 bzoj 1486 最小圈

直接放链接吧,题目是图片
https://www.lydsy.com/JudgeOnline/problem.php?id=1486

题解

二分平均值x
01分数规划

x>=ki=1wk x >= ∑ i = 1 k w k

i=1kwx<=0 ∑ i = 1 k w − x <= 0

那么我们把边的权值改为w-x,跑dfsSPFA判负环check即可

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=2e5+5;
struct node{
    int u,v,nxt;
    double w;
}e[M],edge[M];
int head[N],mcnt;
void add_edge(int u,int v,double w){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].w=w;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
bool vis[N];
double dist[N];
int n,m;
bool fuquanhuan;
void SPFA(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        double w=edge[i].w;
        if(dist[u]+w<dist[v]){
            if(vis[v]){
                fuquanhuan=true;
                break ;
            }
            else{
                dist[v]=dist[u]+w;
                SPFA(v);
            }
        }
    }
    vis[u]=0;
}
void build_map(double mid){
    memset(head,0,sizeof head);
    mcnt=0;
    for(int i=1;i<=m;i++){
        add_edge(e[i].u,e[i].v,e[i].w-mid);
    }
}
bool check(double mid){
    build_map(mid);
    memset(vis,0,sizeof vis);
    memset(dist,0,sizeof dist);
    fuquanhuan=false;
    for(int i=1;i<=n;i++){
        SPFA(i);
        if(fuquanhuan)
            return false;
    }
    return true;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%lf",&e[i].u,&e[i].v,&e[i].w);
    }
    double l=-10000000,r=10000000;
    int times=100;
    while(times--){
        double mid=(l+r)/2.0;
        if(check(mid))
            l=mid;
        else
            r=mid;
    }
    printf("%.8lf\n",l);
}

习题1 bzoj 2118 墨墨的等式

墨墨突然对等式很感兴趣,他正在研究 a1x1+a2x2++anxn=B a 1 x 1 + a 2 x 2 + … + a n x n = B 存在非负整数解的条件,他要求你编写一个程序,给定N、{an}、以及B的取值范围,求出有多少B可以使等式存在非负整数解。

题解

算了我懒了
https://www.cnblogs.com/MashiroSky/p/5988262.html

习题2 bzoj 1922 大陆争霸

题目大意:
n个点m条边的带权有向图,你在1节点有无限个自爆机器人,有的点是被一些点所保护的,当所有保护这个点的点都已经被炸掉了之后,你才可以进入那个城市
现在问你最短炸掉n号节点的时间,保证有解

题解

定义d1表示到达i节点的最短时间
d2表示炸掉所有保护i的节点的最短时间
那么dist=max(d1,d2)
答案是dist[n]

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=3005,M=1e6;
typedef long long ll;
struct node{
    ll u,v,w,nxt;
}edge[M];
ll head[N],mcnt;
void add_edge(ll u,ll v,ll w){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].w=w;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
struct mode{
    ll u;
    ll val;
    mode(ll _u,ll _val){
        u=_u;
        val=_val;
    }
};
bool operator <(mode a,mode b){
    return a.val>b.val;
}
priority_queue<mode>q;
ll pro[N];
ll dist[N];
ll d1[N],d2[N];
vector<int>p[N];
bool vis[N];
void Dijkstra(){
    memset(dist,-1,sizeof dist);
    memset(d1,-1,sizeof d1);
    memset(d2,-1,sizeof d2);
    dist[1]=0;
    d1[1]=0;
    d2[1]=0;
    q.push(mode(1,0));
    while(!q.empty()){
        ll u=q.top().u;
        ll t=q.top().val;
        q.pop();
        //if(t>dist[u]&&dist[u]!=-1)
        if(t>dist[u]||vis[u])
            continue ;
        vis[u]=1;
        for(ll i=head[u];i;i=edge[i].nxt){
            ll v=edge[i].v,w=edge[i].w;
            if(d1[v]==-1||d1[v]>dist[u]+w){
                d1[v]=dist[u]+w;
                if(pro[v]==0){
                    dist[v]=max(d1[v],d2[v]);
                    q.push(mode(v,dist[v]));
                }
            }
        }
        for(ll i=0,sz=p[u].size();i<sz;i++){
            ll v=p[u][i];
            pro[v]--;
            d2[v]=max(d2[v],dist[u]);
            if(pro[v]==0&&d1[v]!=-1&&dist[v]==-1){
                dist[v]=max(d1[v],d2[v]);
                q.push(mode(v,dist[v]));
            }
        }
    }
}
int main()
{
    ll n,m;
    scanf("%lld%lld",&n,&m);//ll
    for(ll i=1;i<=m;i++){
        ll u,v,w;
        scanf("%lld%lld%lld",&u,&v,&w);
        add_edge(u,v,w);
    }
    for(ll i=1;i<=n;i++){
        scanf("%lld",&pro[i]);
        for(ll j=1;j<=pro[i];j++){
            ll x;
            scanf("%d",&x);
            p[x].push_back(i);
        }
    }
    Dijkstra();
    ll ans=dist[n];
    if(ans==-1)
        ans/=0;
    printf("%lld\n",ans);
}

习题3 bzoj 2763 飞行路线

题目大意:n个点,m条带权无向边,你最多可以免费走k条边,问从s到t的最小花费 k<=10

题解

注意到k<=10
我们定义二维的dist数组然后跑最短路即可

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=10005,M=100005,K=15;
typedef long long ll;
typedef pair<int,int> pii;
struct node{
    int u,v,w,nxt;
}edge[M];
int head[N],mcnt;
void add_edge(ll u,ll v,ll w){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].w=w;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
int dist[N][K];
bool inqueue[N][K];
queue<pii> q;
int s,t;
int n,m,k;
int ans=1<<30;
void SPFA(){
    memset(dist,-1,sizeof dist);
    dist[s][0]=0;
    inqueue[s][0]=1;
    q.push(pii(s,0));
    while(!q.empty()){
        int u=q.front().first,p=q.front().second;
        q.pop();
        if(u==t){
            ans=min(ans,dist[u][p]);
        }
        inqueue[u][p]=0;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            if(dist[v][p]==-1||dist[v][p]>dist[u][p]+w){
                dist[v][p]=dist[u][p]+w;
                if(!inqueue[v][p]){
                    q.push(pii(v,p));
                    inqueue[v][p]=1;
                }
            }
            if(p<k&&(dist[v][p+1]==-1||dist[v][p+1]>dist[u][p])){
                dist[v][p+1]=dist[u][p];
                if(!inqueue[v][p+1]){
                    q.push(pii(v,p+1));
                    inqueue[v][p+1]=1;
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    SPFA();
    printf("%d\n",ans);
}

习题4 bzoj 4016 最短路径树问题

我还没做…QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值