图论8-最短路练习

第一道题:P1186 玛丽卡

题意分析:在一个图中随便求任意断了一条边的最短路的最大值。(最大值是因为有多种断边的方案)。

思路解析:首先是大暴力,根据题目要求模拟就行了。就是依次枚举每条边,再断开这条边并跑一遍 spfa 或 dijkstra

,这样的时间复杂度是 O(m2logm) 可以说妥妥的T。

但是千里之行始于足下,正解还是要建立在暴力的基础上的。所以现在开始想一想能怎么优化,首先可以确定 spfa 和

dijkstra 是没法再优化了。所以优化的肯定就是断边的方案,什么样的边肯定不会断呢?可以确定,不在 1-n 最短路上

的边一定不会断,而这个最短路是其中任意一个最短路就好了。因为如果一条边不在 1-n 的所有最短路上的话断了也是

白断,只要继续走这条最短路就好了。所以只有在最短路上的边才有被断的“资格”。那怎么确定一条边是不是在最短

路上呢?可以记录每个点的前缀节点,也就是它可以有什么节点过来是最短路。然后从n开始遍历前缀节点,直到 1 号节

点,这样遍历的这条路径就是一条最短路。这里注意,有可能有很多最短路,但是只用记录其中一条即可,因为断的边必

须在所有最短路上才行。

具体步骤:

1: 建图

2: 一遍spfa,找到所有点的前缀节点。

3: 之后从n枚举,找到最短路径上的边,并删除(标记)这条边,再开始跑 spfa 或 dijkstra (但我写的是 spfa )。

4: 记录并输出答案

 

完整代码如下:

 

#include<bits/stdc++.h>
using namespace std;
const int NR=1005;
const int MR=1e6+10;
int n,m;
int to[MR],nxt[MR],val[MR];
int head[NR];
int tot=1;//邻接表 
void add(int x,int y,int z)
{
    to[tot]=y;
    val[tot]=z;
    nxt[tot]=head[x];
    head[x]=tot++;
}
bool flag[NR][NR];//标记数组,表示i—>j这条边被删除了 
bool vis[NR];
int la[NR];//前缀节点 
int dis[NR];
int ans;
void spfa(bool f)//正常spfa,但是bool f的作用是是否计算la,也就是前缀节点 
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(1);
    dis[1]=0;
    vis[1]=1;
    while(!q.empty())
    {
        int x=q.front();q.pop();vis[x]=0;
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(flag[x][y]) continue;
            if(dis[y]>dis[x]+val[i])
            {
                if(f) la[y]=x;
                dis[y]=dis[x]+val[i];
                if(!vis[y])
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
}
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
int main()
{
//  freopen("1.in","r",stdin);
//  freopen("1.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        add(x,y,z);add(y,x,z);
    }
    spfa(1);
    for(int i=n;i;i=la[i])
    {
        flag[la[i]][i]=1;
        flag[i][la[i]]=1;//标记 
        spfa(0);
        flag[la[i]][i]=0;
        flag[i][la[i]]=0;//解除标记 
        ans=max(ans,dis[n]);//更新答案 
    }
    printf("%d",ans);
    return 0;
}

 

第二题:P1462 通往奥格瑞玛的道路

题意简述:有一个图,其中每个点和边都有一个权值,求在边权和不大于 b 时经过的最大点权的最小值是多少。

思路解析:首先可以确定这个题一定是个与最短路有关的题(非常明显),但这道题和正常的最短路模板比起来就多了

一个点权限制,否则就是一个模板题。所以我们要将不会的转化为会的,所以我们先忽略点权的事情,相信大家都会做。

就只要跑一遍最短路就行了。

但如果已经规定了最大点权那我们会不会呢?当然是会的,只要有一些点不能走不就行了,那么就是遍历到一个节点后

如果发现这个点的权值大于最大的权值,那么就直接跳过这个点。

讲到这里,相信大家都会做这道题了,怎么将原题规定最大点权呢?当然是二分答案啦!二分最大点权然后规定点权后跑

spfa 然后看到 dis[n] 的值是否大于b,来判断这个最大权值是否合法。

这个算法的时间复杂度是 O(km×log(max{c_i}-min{c_i}) 是能过的。

再来理一下思路

step 1: 读入建图

step 2: 二分最大点权值

step 3: 在 step2 的点权限制下跑 spfa ,最后再检查 dis[n] 是否大于 b。

step 4: 输出最小二分值或 AFK。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int NR=1e4+10;
const int MR=1e5+10;
int n,m,b;
int a[NR];
int to[MR],nxt[MR],val[MR];
int head[NR];
int tot=1;
void add(int x,int y,int z)
{
    to[tot]=y;
    val[tot]=z;
    nxt[tot]=head[x];
    head[x]=tot++;
}
bool vis[NR];
int dis[NR];
bool spfa(int num)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    queue<int> q;q.push(1);
    dis[1]=0;vis[1]=1;
    while(!q.empty())
    {
        int x=q.front();q.pop();vis[x]=0;
        if(a[x]>num) continue;
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(a[y]>num) continue;
            if(dis[y]>dis[x]+val[i])
            {
                dis[y]=dis[x]+val[i];
                if(!vis[y])
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
    return dis[n]<b;
}
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
int main()
{
//  freopen("1.in","r",stdin);
//  freopen("1.out","w",stdout);
    n=read(),m=read(),b=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        add(x,y,z);add(y,x,z);
    }
    int l=0,r=1000000000,ans=-1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(spfa(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    if(ans>0) printf("%d\n",ans);
    else puts("AFK");
    return 0;
}

  

posted @ 2020-04-03 22:11  CZD648  阅读(202)  评论(0编辑  收藏  举报
Live2D