acm训练专题四:最短路练习

目录

专题链接

A-Til the Cows Come Home

B-Frogger

C-Heavy Transportation

D-Silver Cow Party

E-Currency Exchange

F-Wormholes

G-MPI Maelstrom

H-Cow Contest

I-Arbitrage

J-Invitation Cards

K-Candies

L-Subway

M-昂贵的聘礼

N-Tram

O-Extended Traffic

P-Layout


第一次全部写完 写了好久好久我真的好笨呜呜呜

但是我还是要夸夸自己

一直想要总结但是我太懒了qaq 一直忘记写 刚考完蓝桥杯不想干别的事情就先来把这篇博客写完吧~

专题链接

HNCU寒假训练专题四: 最短路练习 - Virtual Judge (vjudge.net)

A-Til the Cows Come Home

模板题了没什么好说的
堆优化版的dijkstra模板默一遍就好了

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=4010;//注意这里要记得开4010 因为是双向边 之前一直只开了2010一直Runtime Error 每次都会忘记qaq
typedef pair<int,int> PII;
int h[N],e[N],ne[N],w[N],idx;
int dis[N];
bool st[N];
int n,m;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
    priority_queue<PII,vector<PII>,greater<PII>>q;
    memset(dis,0x3f,sizeof dis);
    q.push(make_pair(0,1));
    dis[1]=0;
    while(!q.empty())
    {
        PII t=q.top();
        int distance=t.first;
        int ver=t.second;
        q.pop();
        if(st[ver])
            continue;
        if(ver==n)
            break;
        st[ver]=true;
        for(int i=h[ver];~i;i=ne[i])
        {
            int j=e[i];
            dis[j]=min(dis[j],dis[ver]+w[i]);
            q.push(make_pair(dis[j],j));
        }
    }
    return dis[n];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>m>>n;
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    cout<<dijkstra()<<endl;
}

B-Frogger

题意:有n个石头,青蛙a要去找青蛙b,它可以直接跳到青蛙b的石头上,也可以经过其他石头。使它单次跳跃最长距离尽可能短,求这个最长距离的最小可能值。

分析:这里直接跑一遍最短路就好了,我用的dijkstra。只是这里的dis[ ]数组不再表示到当前点的各边距离之和,而是从起点到当前点的路程中最长边的距离是多少。

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int INF=0x3f3f3f3f;
const int N=210;
int ch;//第几次
int n;
double dis[N];
double g[N][N];
bool vis[N];
vector<PII>vec;
double get_dis(PII a,PII b)//求a石头和b石头间的距离
{
    int x1=a.first,x2=b.first;
    int y1=a.second,y2=b.second;
    return (double)sqrt((double)(x1-x2)*(x1-x2)+(double)(y1-y2)*(y1-y2));
}
double dijkstra()
{
    memset(g,0,sizeof g);
    memset(vis,false,sizeof vis);
    //初始化所有每两个石头之间的距离
    for(int i=0;i<n;++i)
        for(int j=i+1;j<n;++j)
            g[i][j]=g[j][i]=get_dis(vec[i],vec[j]);
    for(int i=0;i<n;++i)
        dis[i]=INF;
    dis[0]=0;
    for(int i=0;i<n;++i)
    {
        int t=-1;
        for(int j=0;j<n;++j)
        {
            if(!vis[j]&&(t==-1||dis[j]<dis[t]))
                t=j;
        }
        vis[t]=true;//已经是当前跨越距离最小的一个点了
        for(int j=0;j<n;++j)
            dis[j]=min(dis[j],max(dis[t],g[t][j]));
    }
    return dis[1];
}
int main()
{
    while(cin>>n,n)
    {
        vec.clear();
        for(int i=0;i<n;++i)
        {
            int a,b;
            cin>>a>>b;
            vec.push_back(make_pair(a,b));
        }
        cout<<"Scenario #"<<++ch<<endl;
        printf("Frog Distance = %.3f\n\n",dijkstra());
    }
}

C-Heavy Transportation

题意:要从点1到点n运货物,每条边有一个最大承重量,求一次最多能运多少货物。

分析:由题意可知求的是最长路,和普通的最短路没有太大区别,只要注意初始化和进行松弛操作的时候稍微改一下就好了。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int g[N][N];
int dis[N];
bool st[N];
int n;
int dijkstra()
{
    for(int i=1;i<=n;++i)
        dis[i]=g[1][i];
    st[1]=true;
    for(int i=2;i<=n;++i)
    {
        int t=-1;
        for(int j=1;j<=n;++j)
                if(!st[j]&&(t==-1||dis[t]<dis[j]))
                    t=j;
        st[t]=true;
        for(int j=1;j<=n;++j)
            dis[j]=max(dis[j],min(dis[t],g[t][j]));
    }
    return dis[n];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    for(int i=1;i<=t;++i)
    {
        int m;
        //因为有多组测试数据 所以这里的初始化一定不能漏
        memset(dis,0,sizeof dis);
        memset(st,false,sizeof st);
        memset(g,0,sizeof g);
        cin>>n>>m;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            g[a][b]=g[b][a]=c;
        }
        cout<<"Scenario #"<<i<<":"<<endl;
        cout<<dijkstra()<<endl<<endl;
    }
}

D-Silver Cow Party

题意:n头牛要举办一场派对,地点定在其中一头牛的家中。求这n头牛去参加派对到回家(来回)的最短路径。(题目给的是有向边)

分析:从题目中分析,我们想求所有点到x的最短距离,和x到所有点的最短距离。很明显,前者直接跑一遍dijkstra,而对于后者可以跑n遍dijkstra。但是仔细想,x到所有点的最短距离,如果把所有的边都反一下,其实是不是就也可以转化为所有点到x的距离呢?所以对于这样的问题,只要反向建边,然后再跑一遍dijkstra。不过因为要跑两次最短路,所以不要忘记初始化qaq

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e3+10;
int dis[N];
int udis[N];
int n,m,x;
int g[N][N];
bool st[N];
void dijkstra1()
{
    memset(dis,0x3f,sizeof dis);
    memset(st,false,sizeof st);
    dis[x]=0;
    for(int i=1;i<=n;++i)
    {
        int t=-1;
        for(int j=1;j<=n;++j)
            if((t==-1||dis[j]<dis[t])&&!st[j])
                t=j;
        st[t]=true;
        for(int j=1;j<=n;++j)
            dis[j]=min(dis[j],dis[t]+g[t][j]);
    }
}
void dijkstra2()
{
    memset(udis,0x3f,sizeof udis);
    memset(st,false,sizeof st);
    udis[x]=0;
    for(int i=1;i<=n;++i)
    {
        int t=-1;
        for(int j=1;j<=n;++j)
            if((t==-1||udis[j]<udis[t])&&!st[j])
                t=j;
        st[t]=true;
        for(int j=1;j<=n;++j)
            udis[j]=min(udis[j],udis[t]+g[t][j]);
    }
}
int main()
{
    cin>>n>>m>>x;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            g[i][j]=(i==j?0:1e9);
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=c;
    }
    dijkstra1();
    for(int i=1;i<n;++i)
        for(int j=i+1;j<=n;++j)
            swap(g[i][j],g[j][i]);
    dijkstra2();
    int maxx=0;
    for(int i=1;i<=n;++i)
        maxx=max(dis[i]+udis[i],maxx);
    cout<<maxx<<endl;
}

E-Currency Exchange

题意:题意好复杂不想描述了qaq)有n种货币和m个货币兑换点,每个兑换点都有6个信息,A货币,B货币,A兑换到B的汇率,A兑换到B的服务费,B兑换到A的汇率,B兑换到A的服务费。例:A到B的兑换汇率为29.75,服务费是0.39,则用100元A货币可以兑换(100-0.39)*29.75=2963.3975元B货币。现有一个人有v元s货币,他希望能通过兑换的方式,使自己手上的钱变多(最后还希望换回s货币)问他能否做到

分析:因为还要换回s货币,所以一定是有个环的,这个环的权值我们希望是为正的。那这题其实就是判断有无正环,转换到这里是不是要立马想到spfa?把判断负环的模板改一下就好了。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

const int N = 110;
const int M = 210;
int h[N], e[M], ne[M], idx;
double wr[M],wc[M];
double dis[N];
bool st[N];
int cnt[N];
int n,m,s;
double v;

using namespace std;

void add(int a,int b,double r,double c)
{
    wr[idx]=r,wc[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
    queue<int>q;
    for(int i=1;i<=n;++i)
    q.push(s),st[s]=true;
    dis[s]=v;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]<(dis[t]-wc[i])*wr[i])
            {
                dis[j]=(dis[t]-wc[i])*wr[i];
                if(!st[j])
                {
                    st[j]=true;
                    q.push(j);
                }
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)//如果这个点更新了n次,说明存在正环
                    return true;
            }
        }
    }
    if(dis[s]>v)
        return true;
    return false;
}
int main()
{
    cin>>n>>m>>s>>v;
    memset(h,-1,sizeof h);
    while (m -- )
    {
        int a,b;
        double rab,cab,rba,cba;
        cin>>a>>b>>rab>>cab>>rba>>cba;
        add(a,b,rab,cab);
        add(b,a,rba,cba);
    }
    if(spfa())
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
}

F-Wormholes

题意:有n片田地,m条路径(双向),w个虫洞(单向),经过路径会消耗一定的时间,经过虫洞时间会提前一些。现在约翰想从某片田地出发,经过一些路径和虫洞回到他的出发地,并且时间为出发之前的时刻。

分析:从出发地出发,最后还要回到出发地,其实其实很明显又是一个环,问时间能否提前,就是判断负环。跑遍spfa一起ac

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=510;
int g[N][N];
int dis[N];
bool st[N];
int cnt[N];
int n,m,w;
bool spfa(int x)
{
    queue<int>q;
    for(int i=1;i<=n;++i)
      st[i]=true,q.push(i);
    dis[x]=0;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=1;i<=n;++i)
        {
            if(dis[i]>dis[t]+g[t][i])
            {
                dis[i]=dis[t]+g[t][i];
                if(!st[i])
                {
                    q.push(i);
                    st[i]=true;
                }
                cnt[i]=cnt[t]+1;
                if(cnt[i]>=n)
                    return true;
            }
        }
    }
    if(dis[x]<0)
        return true;
    return false;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n>>m>>w;
        memset(g,0x3f,sizeof g);
        memset(dis,0x3f,sizeof dis);
        memset(cnt,0,sizeof cnt);
        memset(st,false,sizeof st);
        while(m--)
        {
            int s,e,t;
            cin>>s>>e>>t;
            g[s][e]=g[e][s]=min(g[e][s],t);//注意这题存在重边
        }
        while(w--)
        {
            int s,e,t;
            cin>>s>>e>>t;
            g[s][e]=-t;
        }
        spfa(1)?cout<<"YES":cout<<"NO";
        cout<<endl;
    }
}

G-MPI Maelstrom

题意:有n台服务器,一些服务器可以相互传递信息,求 从1号服务器发出信息 传递到其他所有服务器所花费的时间。

分析:这题就是要稍微转换下权值。还有就是这题跟普通最短路不太一样,这里求的是1号点到所有点所花费的最短时间。因为它是一起发送的,所以只要求出1号点到每个点的最短距离,再取max就好了。

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int N = 110;
int g[N][N];
int dis[N];
bool st[N];
int n;
int dijkstra()
{
    dis[1]=0;
    int ans=0;
    for(int i=1;i<=n;++i)
    {
        int t=-1;
        for(int j=1;j<=n;++j)
        {
            if(!st[j]&&(t==-1||dis[j]<dis[t]))
               t=j;
        }
        st[t]=true;
        for(int j=1;j<=n;++j)
        {
            dis[j]=min(dis[j],dis[t]+g[t][j]);
        }
    }

    for(int i=1;i<=n;++i)
        ans=max(dis[i],ans);
    return ans;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            g[i][j]=(i==j?0:1e9);
    memset(dis,0x3f,sizeof dis);
    for(int i=2;i<=n;++i)
    {
        string s;
        for(int j=1;j<=i-1;++j)
        {
            cin>>s;
            if(s=="x")
                continue;
            else
            {
                int num=0;
                for(int i=0;i<s.size();++i)
                    num=(s[i]-'0')+10*num;
                g[i][j]=g[j][i]=num;
            }
        }
    }
    cout<<dijkstra()<<endl;
}

H-Cow Contest

题意:n头奶牛一起比赛,已进行了m次两两对决并给出了结果。求有多少奶牛通过这m个结果可以确定具体排名。

分析:Floyd的传递闭包问题。

#include<iostream>
using namespace std;
const int N=110;
const int M=4510;
int d[N][N];//a>b
int ud[N][N];//b>a
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n,m;
    cin>>n>>m;
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        d[a][b]=ud[b][a]=1;//标记关系
    }
    for(int k=1;k<=n;++k)
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
            {
                d[i][j]|=(d[i][k]&&d[k][j]);//看是否有传递关系
                ud[i][j]|=(ud[i][k]&&ud[k][j]);
            }
    int ans=0;
    for(int i=1;i<=n;++i)
    {
        int cnt=0;
        for(int j=1;j<=n;++j)
            if(d[i][j]||ud[i][j])
                cnt++;
        if(cnt==n-1)//可以比较其他n-1个点的关系,说明最终排名可以知道
            ans++;
    }
    cout<<ans<<endl;
}

I-Arbitrage

题意:给了n个货币,和m个可以进行的交换。问套利是否可行。

分析:因为这里n<=30,数据范围很小,所以可以用Floyd

#include<iostream>
#include<cstring>
#include<map>
#include<string>
using namespace std;
const int N=35;
double g[N][N];
int main()
{
    int n;
    int cnt=1;
    while(cin>>n,n)
    {
        memset(g,0,sizeof g);
        map<string,int>mp;
        for(int i=1;i<=n;++i)
        {
            string s;
            cin>>s;
            mp[s]=i;
            g[i][i]=1;//初始化一开始自己的钱都为1
        }
        int m;
        cin>>m;
        while(m--)
        {
            string s1,s2;
            double x;
            cin>>s1>>x>>s2;
            g[mp[s1]][mp[s2]]=x;
        }
        //Floyd
        for(int k=1;k<=n;++k)
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                    g[i][j]=max(g[i][j],g[i][k]*g[k][j]);
        printf("Case %d: ",cnt++);
        bool flag=false;
        for(int i=1;i<=n;++i)
            if(g[i][i]>1)//看最后钱有没有变多
            {
                cout<<"Yes"<<endl;
                flag=true;
                break;
            }
        if(!flag)
            cout<<"No"<<endl;
    }
}

J-Invitation Cards

题意:acm学生成员要去进行宣传,他们从ccs出发到各个站点,再从各个站点回来,路上需要花费车费,求最少花费的钱。

分析:因为站点数和道路数数据范围一样,并且是单源最短路,所以要用堆优化版的dijktra,这题很像之前的奶牛派对,用反向建图的方法就能解决掉从各个站点回来的问题。不同的是他们的存储方式不同,这题用的是邻接表。邻接表的反向建图多开一个rh[ ]的头结点表就好了。

#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
typedef pair<long long, int> PII;

const int N = 1e6+10;
const int M = 2*N;
int h[N], rh[N],e[M], w[M], ne[M], idx;//这里不要全部都开M,不然会超内存
long long dis[N];
bool st[N];
int n,m;
void add(int h[] ,int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
long long dijkstra(int h[])
{
    memset(dis,0x3f,sizeof dis);
    memset(st,false,sizeof st);
    dis[1]=0;
    priority_queue<PII,vector<PII>,greater<PII> >q;
    q.push(make_pair(0,1));
    while(!q.empty())
    {
        PII t=q.top();
        q.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(dis[j]>dis[ver]+w[i])//这里如果不判断一下直接取min的话会超时的
            {
                dis[j]=dis[ver]+w[i];
                q.push(make_pair(dis[j],j));
            }
        }
    }
    long long ans=0;
    for(int i=1;i<=n;++i)
        ans+=dis[i];
    return ans;
}
int main()
{
    int t;
    scanf("%d ",&t);
    while(t--)
    {
        memset(h, -1, sizeof h);
        memset(rh,-1,sizeof rh);
        idx=0;
        
        scanf("%d%d",&n,&m);
        while (m -- )
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(h, a, b, c);
            add(rh, b, a, c);//邻接表建反图多用一个rh[]数组就好了
        }
        long long res=dijkstra(h);
        //反向建图
        res+=dijkstra(rh);
        printf("%lld\n",res);
    }
}

K-Candies

题意:有n个小朋友,老师要给他们分糖果。小朋友间有攀比信息,输入a,b,c,表示小朋友b的糖果最多比小朋友a的糖果多c,否则就会生气。求让所有小朋友满意的情况下,小朋友n最多比小朋友1多分到多少个糖果。

分析:这是一道差分约束的题。根据差分约束的结论,如果求的是最小值,就求最长路,如果求的是最大值,就求最短路。xb<=xa+c,可以转化为,从a小朋友走到b小朋友,有着长度为c的一条边。根据题意,是要求最多,所以求遍最短路就好了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 3e4+10;
const int M = 2e5;
int h[N],ne[M],e[M],w[M],idx;
bool st[N];
int dis[N];
int n,m;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra()
{
    memset(dis,0x3f,sizeof dis);
    memset(st,false,sizeof st);
    priority_queue<PII,vector<PII>,greater<PII> >heap;
    dis[1]=0;
    heap.push(make_pair(0,1));
    while(!heap.empty())
    {
        PII t=heap.top();
        heap.pop();
        int ver=t.second;
        if(st[ver])
            continue;
        st[ver]=true;
        if(ver==n)
            break;
        for(int i=h[ver];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]>dis[ver]+w[i])
            {
                dis[j]=dis[ver]+w[i];
                heap.push(make_pair(dis[j],j));
            }
        }
    }
    return dis[n];
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d\n",dijkstra());
}

L-Subway

题意:已知步行速度是10km/h,地铁速度是40km/h。给出家和学校坐标,以及一些双向的地铁线路,求从家到学校的所要花费的最短时间。

分析:站点最多有200个,可以Floyd。先把所有通地铁的地点路程算出来,然后再算出走路路程取min,跑遍Floyd,连通所有的点,最后转化为时间。

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
typedef pair<double, double> PII;
const int N =210;
PII a[N];
double d[N][N];
double dis(int m,int n)
{
    double dx1,dx2,dy1,dy2;
    dx1=a[m].first;
    dy1=a[m].second;
    dx2=a[n].first;
    dy2=a[n].second;
    return sqrt((dx1-dx2)*(dx1-dx2)+(dy1-dy2)*(dy1-dy2));
}
int main()
{
    for(int i=0;i<N;++i)
        for(int j=0;j<N;++j)
            d[i][j]=(i==j?0:1e9);
    double x1,x2,y1,y2;
    cin>>x1>>y1>>x2>>y2;
    a[0]={x1,y1};
    a[1]={x2,y2};
    double x,y;
    int k=2;
    int cnt=2;
    while(cin>>x>>y)
    {
        if(x!=-1&&y!=-1)
            a[k++]=make_pair(x,y);
        else
        {
            for(int i=cnt;i<k-1;++i)
                for(int j=i+1;j<k;++j)
                   d[i][j]=d[j][i]=d[i][j-1]+dis(j-1,j)/4;//A->C地铁的时间等于A->B地铁的时间加B->C的地铁时间;/4是因为地铁时间比走路时间快4倍
            cnt=k;
        }
    }
    //加上走路时间,算出所有地点之间的时间
    for(int i=0;i<k;++i)
        for(int j=0;j<k;++j)
            d[i][j]=min(d[i][j],dis(i,j));
    //Floyd
    for(int m=0;m<k;++m)
        for(int i=0;i<k;++i)
            for(int j=0;j<k;++j)
                d[i][j]=min(d[i][j],d[i][m]+d[m][j]);
    cout<<(int)(d[0][1]*0.006+0.5)<<endl;//换算成时间并且四舍五入
}

M-昂贵的聘礼

题意:一个探险家想向酋长求亲,但是酋长要10000个金币才可以答应把女儿嫁给他,或者如果能弄来一些其他人所拥有的替代品,就可以用更少的钱就行了。其他人也提出了类似的要求。值得注意的是,地位差距超过一定限制的两个人是不可以进行交易的,哪怕间接也不行。求探险家最少需要多少金币才能娶到酋长女儿。

分析:这题建图有点复杂。可以说这题既有点权(原价购买该物品),又有边权(用其他物品优惠购买),且额外存在更新条件,就是等级差距的限制。对于点权的处理方式是多建了一个虚拟源点0,用来表示原价购买该物品,这样就可以把它加进去一起算最短路了。对于这里等级差距的处理方式是对于总区间的每一个点都枚举他的区间范围,如果在区间范围才会更新距离

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 110;
int n,m;
bool st[N];
int dis[N];
int level[N];
int w[N][N];
int dijkstra(int l,int r)
{
    memset(st,false,sizeof st);
    memset(dis,0x3f,sizeof dis);
    dis[0]=0;
    for(int i=1;i<=n+1;++i)
    {
        int t=-1;
        for(int j=0;j<=n;++j)//这里要从0开始
            if(!st[j]&&(t==-1||dis[j]<dis[t]))
                t=j;
        st[t]=true;
        for(int j=1;j<=n;++j)//这里从1开始,因为不需要更新dis[0]了
        {
            if(level[j]>=l&&level[j]<=r)
                dis[j]=min(dis[j],dis[t]+w[t][j]);
        }
    }
    return dis[1];
}
int main()
{
    cin>>m>>n;
    memset(w,0x3f,sizeof w);
    for(int i=0;i<=n;++i)
        w[i][i]=0;
    for(int i=1;i<=n;++i)
    {
        int p,l,x;
        cin>>p>>l>>x;
        level[i]=l;
        w[0][i]=min(w[0][i],p);
        while(x--)
        {
            int t,v;
            cin>>t>>v;
            w[t][i]=min(w[t][i],v);
        }
    }
    int res=1e9;
    for(int i=level[1]-m;i<=level[1];++i)//枚举等级差距左端点,就可以保证所有交易都在这个区间
        res=min(res,dijkstra(i,i+m));
    cout<<res<<endl;
}

N-Tram

题意:有n个站点。电车在第i个站点可以驶往ki个其他站点,其中一个站点为默认驶往站点,如果驶往其他站点,则需要进行一次变道。求从a到b最少需要变道的次数。

分析:很常规的单源汇最短路模型,就是这里的边权没有直接给。输入电车行驶信息后,更新每个站点之间的距离。然后跑遍dijkstra

#include<iostream>
#include<cstring>
using namespace std;
const int N = 110;
int n,a,b;
bool st[N];
int dis[N];
int g[N][N];
int dijkstra()
{
    memset(st,false,sizeof st);
    memset(dis,0x3f,sizeof dis);
    dis[a]=0;
    for(int i=1;i<=n;++i)
    {
        int t=-1;
        for(int j=1;j<=n;++j)
            if(!st[j]&&(t==-1||dis[t]>dis[j]))
                t=j;
        st[t]=true;
        for(int j=1;j<=n;++j)
            dis[j]=min(dis[j],dis[t]+g[t][j]);
    }
    return(dis[b]==0x3f3f3f3f?-1:dis[b]);
}

int main()
{
    cin>>n>>a>>b;
    memset(g,0x3f,sizeof g);
    for(int i=1;i<=n;++i)
        g[i][i]=0;
    for(int i=1;i<=n;++i)
    {
        int k,p;
        cin>>k;
        if(k>0)//因为k有可能等于0
        {
            cin>>p;//输入默认站点
            g[i][p]=0;//不需要变道
            for(int j=2;j<=k;++j)
            {
                cin>>p;
                g[i][p]=1;
            }
        }
    }
    cout<<dijkstra()<<endl;
}

O-Extended Traffic

是错了十五遍最后求助shm的O题🆘

题意:有n个交汇点,每个交汇点都有一个繁忙程度。人们从一个点到另一个点需要缴纳的金额数目是是(目的地繁忙程度-起点的繁忙程度)³,求某个人从零号点到其他点需要交的最小钱数。

分析:因为边权是立方,所以本题会存在负权边,且没有边数限制,所以用spfa。如果存在负权回路,那与这个负环相连的所有点获得钱数一定会是无穷小。由于本题要找出所有负环,不是简单的判断负环,所以当找到负环的时候,不用退出,先用dfs找到所有与这个负环连通的点,然后继续找别的负环。

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=210;
const int M=4e4+10;
int busy[N];
int h[N],w[M],e[M],ne[M],idx;
int dis[N];
bool st[N];
bool vis[N];//标记是否经过负环
int cnt[N];
int n;
void add(int a,int b, int c)
{
    w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int x)//标记和负环连通的所有点
{
    vis[x]=true;
    for(int i=h[x];~i;i=ne[i])
    {
        int j=e[i];
        if(!vis[j])
            dfs(j);
    }
}
void spfa()
{
    memset(dis,0x3f,sizeof dis);
    memset(st,false,sizeof st);
    memset(cnt,0,sizeof cnt);
    memset(vis,false,sizeof vis);
    queue<int>q;
    q.push(1);
    dis[1]=0;
    st[1]=true;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(vis[j])//这里一定要加上判断,否则会超时
                continue;
            if(dis[j]>dis[t]+w[i])
            {
                dis[j]=dis[t]+w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                    cnt[j]++;
                    if(cnt[j]>=n)
                        dfs(j);
                }
            }
        }
    }
}
int main()
{
    int t,ch=1;
    cin>>t;
    while(t--)
    {
        memset(h,-1,sizeof h);
        idx=0;
        int m;
        cin>>n;
        for(int i=1;i<=n;++i)
            cin>>busy[i];
        cin>>m;
        while(m--)
        {
            int a,b;
            cin>>a>>b;
            int w=busy[b]-busy[a];
            add(a,b,w*w*w);
        }
        spfa();
        int q;
        cin>>q;
        printf("Case %d:\n",ch++);
        while(q--)
        {
            int x;
            cin>>x;
            if(dis[x]==0x3f3f3f3f||dis[x]<3||vis[x])
                cout<<"?"<<endl;
            else
                cout<<dis[x]<<endl;
        }
    }
}

P-Layout

题意:n头奶牛在排队,一些奶牛互相存在好感,他们之间的距离不能超过一个值。也有一些奶牛相互方案,他们之间的距离不能小于一个值。如果没有符合要求的方案输出-1,如果1号奶牛和n号奶牛的距离可以任意大输出-2,否则输出1号奶牛和2号奶牛之间可能的最大距离。

分析:一道差分约束的题目
问题1:不存在满足要求的方案
解析:即存在负环
方案:用spfa判断负环

问题2:1号奶牛和N号奶牛之间的距离可以任意大
解析:将1号点定点为dis[1]=0,即dis[n]=0x3f3f3f3f
方案:求出最短路,判断dis[n]是否正无穷

问题3:求i号奶牛和n号奶牛间的可能的最大距离
方案:即dis[1]=0,求dis[n]。因为是求最大距离,所以统一换成<=的形式,求最短路。

因为本来就会用到spfa判断负环,所以求最短路也可以用spfa算法,判断负环需要把所有点先入队,求n号奶牛到1号奶牛的距离,只需要把dis[1]=0就好了,两个不冲突,所以只要跑一次spfa就好了

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n;
const int N = 1010;
const int M = 21010;
int h[N],e[M],ne[M],w[M],idx;
bool st[N];
int dis[N];
int cnt[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
    memset(st,false,sizeof st);
    memset(dis,0x3f,sizeof dis);
    memset(cnt,0,sizeof cnt);
    queue<int>q;
    dis[1]=0;
    for(int i=1;i<=n;++i)
    {
        st[i]=true;
        q.push(i);
    }
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]>dis[t]+w[i])
            {
                dis[j]=dis[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)
                    return true;
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }

            }
        }
    }
    return false;
}
int main()
{
    int m1,m2;
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m1>>m2;
    memset(h, -1, sizeof h);
    for(int i=1;i<=n-1;++i)
        add(i+1,i,0);
    while(m1--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        if(b<a)
            swap(a,b);
        else
            add(a,b,c);
    }
    while(m2--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        if(b<a)
            swap(a,b);
        else
            add(b,a,-c);
    }
    if(spfa())
        cout<<-1<<endl;
    else
    {
        if(dis[n]==0x3f3f3f3f)
            cout<<-2<<endl;
        else
            cout<<dis[n]<<endl;
    }
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值