最短路总结

Posted wtz2333

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最短路总结相关的知识,希望对你有一定的参考价值。

这一段时间复习了一下最短路,做了几道非常典型特别考察最短路性质的题

1.P1144 最短路计数

这个题主要考察对松弛操作的理解。
关键代码

    if(dis[v] > dis[u] + 1)
    {
       dis[v] = dis[u] + 1;
       ans[v] = ans[u];
           q.push(make_pair(dis[v],v));
    }
    else if(dis[v] == dis[u] + 1)
    {
       ans[v] += ans[u];
       ans[v] %= mo;
    }

2.CF786B Legacy

线段树优化建边,对于一个点,与一个连续区间里的点连边,可以利用线段树的特性来降低连边复杂度。

void build1(int &p,int l,int r)
{
    if(l==r)
    {
       p=l;return;
    }
    p=++tot;
    int mid=l+r>>1;
    build1(lc[p],l,mid);
    build1(rc[p],mid+1,r);
    add(p,lc[p],0);add(p,rc[p],0);
}

3.P1772 [ZJOI2006]物流运输

这是一道DP加最短路的题目,由于数据很小,我们的复杂度可以很高
设计这样的一个DP。f[i] 表示前i天的花费,考虑转移,考虑第j天是否改变航线

方程为:f[i] = min(f[i],f[j-1] + (i-j+1) * x + k);

对于每一次变化,我们都要求一次最短路。

        f[0] = -k;
    for(int i = 1;i <= n;i ++)
    {
       for(int j = 1;j <= m ;j++)flag[j] = 0;
       for(int j = i;j >= 1;j--)
       {
        for(int a = 1;a <= m ;a++)
        if(pd[a][j])flag[a] = 1;
        int x = spfa(1);
        if(x >= dis[0])break;
        f[i] = min(f[i],f[j-1] + (i-j+1)*x + k);
       }
    }

4.P2868 [USACO07DEC]观光奶牛Sightseeing Cows

01分数规划,懒得说了大家自己百度吧,主要是二分加图论结合。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
inline int read()
{
    char ch=getchar();int x=0,f=1;
    while(ch<'0' || ch>'9') {
       if(ch=='-') f=-1;
          ch=getchar();
    }
    while(ch<='9' && ch>='0') {
       x=x*10+ch-'0';
       ch=getchar();
    }
    return x*f;
}
inline ll readl()
{
    char ch=getchar();ll x=0,f=1;
    while(ch<'0' || ch>'9') {
       if(ch=='-') f=-1;
          ch=getchar();
    }
    while(ch<='9' && ch>='0') {
       x=x*10+ch-'0';
       ch=getchar();
    }
    return x*f;
}
const int maxn = 5001;
int n,m;
int a[maxn],vis[maxn],num[maxn];
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
double len[maxn<<1],dis[maxn];
queue<int> q;
void add(int x,int y,int z)
{
   l++;
   pre[l] = last[x];
   last[x] = l;
   other[l] = y;
   len[l] = z;
}
bool spfa(int x,double y)
{
   memset(num,0,sizeof num);
   memset(vis,0,sizeof vis);
   vis[x] = 1;
   memset(dis,0,sizeof dis);
   dis[x] = 0;
   q.push(x);
   while(!q.empty())
   {
      int u = q.front();
      q.pop();
      vis[u] = 0;
      if(num[u] >= n)return 1;
      for(int p = last[u];p;p = pre[p])
      {
         int v = other[p];
         if(dis[v] > dis[u] + y*len[p] - a[u])
         {
            dis[v] = dis[u] + y*len[p] - a[u];
            if(vis[v] == 0)
            {
               vis[v] = 1;
               num[v] ++;
               if(num[v] >= n)return 1;
               q.push(v);
            }
         }
      }
   }
   return 0;
}
bool check(double x)
{
   //cout<<x<<endl;
   for(int i = 1;i <= n ;i++)
   if(spfa(i,x))return 1;
   return 0;
}
int main(){
   n = read(),m = read();
   for(int i = 1;i <= n ;i++)a[i] = read();
   for(int i = 1;i <= m;i++)
   {
      int a = read(),b = read(),c = read();
      add(a,b,c);
   }
   double l = 0,r = 1000000,ans = 0;
   while(r - l >= 0.0001)
   {
      double mid = (l + r) / 2;
      if(check(mid))
      l = mid + 0.0001,ans = mid;
      else r = mid - 0.0001;
   }
   printf("%.2lf",ans);
   return 0;
}

5.P2939 [USACO09FEB]改造路Revamping Trail

分层图最短路板子题,观察题面,发现k很小,我们就可以建k张图,图与图之间建零花销边,最后跑一边最短路就OK了

   for(int i = 1;i <= m ;i++)
   {
      int a = read(),b = read(),c = read();
      add(a,b,c);add(b,a,c);
      for(int j = 1;j <= k ;j ++)
      {
        add(a + j*n,b + j*n,c);
        add(b + j*n,a + j*n,c);
        add(a + (j-1)*n,b + j*n,0);
        add(b + (j-1)*n,a + j*n,0);
      }
   }

6.P1613 跑路

倍增思想与Floyd的结合(f[i][j][p] = (f[i][k][p-1] , f[k][j][p-1]))
(f[i][j][p]) 表示i到j距离是否为(2^{p})

  for(int p = 1;p <= 64;p ++){
        for(int k = 1;k <= n ;k ++){
            for(int i = 1;i <= n ;i ++){
                for(int j = 1;j <= n ;j ++){
                    if(f[i][k][p-1] && f[k][j][p-1]){
                        f[i][j][p] = 1;
                    }
                }
            }
        }
    }

7.P3393 逃离僵尸岛

最短路常见套路,加一个虚拟节点来简化问题,将所有危险城市用虚拟节点连起来统一处理距离小于等于s的,由于多建了一些边,所以要开大一些空间。

   for(int i = 1;i <= k ;i ++){
        int c = read();flag[c] = 1;
        add(n+1,c);add(c,n+1);
    }
   for(int i = 1;i <= n ;i ++)w[i] = p;
    dij(n+1);
    for(int i = 1;i <= n ;i ++){
        if(dis[i] <= s+1){
            w[i] = q;
        }
    }

8.P1606 [USACO07FEB]白银莲花池Lilypad Pond

简单分析,发现这就是一个最短路模型,对于第一问怎么建图都ok,但对于第二问如果将水和荷叶相连会导致计数重复,所以将水和可以通过荷叶到达的水相连,再跑最短路计数

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int n,m,st,ed;
int l,pre[maxn],last[maxn],other[maxn],len[maxn];
int mp[1010][1010],id[1010][1010];
int flag[1010][1010];
ll cnt[maxn];
int vis[maxn],que[maxn],dis[maxn];
int dx[9]={0,-2,-1,1,2,2,1,-1,-2};
int dy[9]={0,1,2,2,1,-1,-2,-2,-1};
void add(int x,int y,int z){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
    len[l] = z;
}
void dfs(int p,int x,int y){
    if(flag[x][y])return ;
    flag[x][y] = 1;
    for(int i = 1;i <= 8;i ++){
        int xx = x + dx[i];
        int yy = y + dy[i];
        if(xx <= 0 || yy <= 0 ||xx > n || yy > m || flag[xx][yy])continue;
        if(mp[xx][yy] == 1)dfs(p,xx,yy);
        flag[xx][yy] = 1;
        add(p,id[xx][yy],1);
    }
}
void spfa(int x){
    memset(dis,0x3f,sizeof dis);
    dis[x] = 0;
    que[1] = x;
    vis[x] = 1;
    cnt[x] = 1;
    int h = 0,t = 1;
    while(h != t){
        h ++;
        int u = que[h];
        vis[u] = 0;
        for(int p = last[u];p;p = pre[p]){
            int v = other[p];
            if(dis[v] == dis[u] + len[p]){
                cnt[v] += cnt[u];
            }
            if(dis[v] > dis[u] + len[p]){
                cnt[v] = cnt[u];
                dis[v] = dis[u] + len[p];
                if(!vis[v]){
                    vis[v] = 1;
                    que[++t] = v;
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n ;i ++){
        for(int j = 1;j <= m;j ++){
            scanf("%d",&mp[i][j]);
            id[i][j] = (i-1)*m + j;
            if(mp[i][j] == 3)st = id[i][j];
            if(mp[i][j] == 4)ed = id[i][j];
        }
    }
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= m;j ++){
            if(mp[i][j] == 2 || mp[i][j] == 4)continue;
            memset(flag,0,sizeof flag);
            dfs(id[i][j],i,j);
        }
    }
    spfa(st);
    if(dis[ed] == dis[0]){
        cout<<-1<<endl;return 0;
    }
    else {
        cout<<dis[ed] - 1<<endl;
        cout<<cnt[ed]<<endl;
    }
    return 0;
}

9.T51485 键盘

题解:分析题目,很像一个DP,但是存在删除键,所以存在后效性,但是可以对每个操作连边跑最短路。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int dis[maxn],vis[maxn];
int n,x;
void spfa(){
    memset(dis,63,sizeof dis);
    queue <int> q;
    if(x >= n){
        dis[0] = 3;dis[n] = x - n;
        q.push(0);
        vis[0] = 1;
    }
    else {
        dis[x] = 0;
        q.push(x);
        vis[x] = 1;
    }
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = 0;
        if(u < n &&dis[u + 1] > dis[u] + 1){
            dis[u + 1] = dis[u] + 1;
            if(!vis[u + 1]){
                vis[u + 1] = 1;
                q.push(u + 1);
            }
        }
        if(u > 1&&dis[u - 1] > dis[u] + 1){
            dis[u - 1] = dis[u] + 1;
            if(!vis[u - 1]){
                vis[u - 1] = 1;
                q.push(u - 1);
            }
        }
        if(u > 0){
            for(int i = 2;u*(i-1) <= n;i ++){
                if(u * i >= n&&dis[n] > dis[u] + 2*(i + 1) + u*i - n){
                    dis[n] = dis[u] + 2*(i + 1) + u*i - n;
                }
                if(u * i < n && dis[u*i] > dis[u] + 2*(i + 1)){
                    dis[u*i] = dis[u] + 2*(i + 1);
                    if(vis[u*i] == 0){
                        vis[u*i] = 1;
                        q.push(u*i);
                    }
                    
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&x,&n);
    spfa();
    printf("%d",dis[n]);
    return 0;
}


以上是关于最短路总结的主要内容,如果未能解决你的问题,请参考以下文章

最短路总结

最短路径

图论 最短路总结

图论----最短路问题

最短路总结

最短路问题常用算法总结和模板