zoukankan      html  css  js  c++  java
  • poj 1724 ROADS

    最短路变形

     题意:你有K个点数,有N个点,M条边,边为有向边,包含4个信息,两个端点+边长+走这条边需要付出的点数。你的任务是,从1号点出发走到n号点,在点数够用的情况下,走出一条最短路,单case

    显然是一个最短路的变形,而且是一种常见的模型。最短路本身是一个求解最优解的问题,在这里加多了一个限制条件,就是点数,所以变为“在一定的限制条件下求解一个最优化问题”的模型,这样的模型,可以由一个大致的套路,就是,在满足限制条件后,再进行更新

    下面将讲3个方法,前两个其实都是BFS,第3个事DFS,是一个记忆化搜索。我们先说BFS

    1.优先队列+dij(最快)

       判断一个元素能否入队,不再是看它的最短路估计值是否被更新,而是从当前点能到达的点,都可以放入队列,在优先队列中,每次取队中最短路估计值最小的元素出来去更新

       如果标号为n的点出队了,那么其实算法结束了,因为之前的状态都没有更新出更小的值,在从现在开始,哪怕再怎么更新,都不会比现在更小了,所以直接跳出,输出即可

    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <queue>
    #include <algorithm>
    using namespace std;
    #define N 110
    #define M 10010
    #define INF 0x3f3f3f3f
    
    int n,m,cost,tot;
    struct State
    {
       int n,d,c;
       bool operator < (const struct State a)const
       {
          if(a.d == d) return a.c < c;
          return a.d < d;
       }
    };
    struct edge
    {
       int u,v,w,c,next;
    };
    typedef struct State State;
    typedef struct edge edge;
    int head[N];
    int d[N];
    edge e[M];
    
    
    void add(int u , int v , int w , int c)
    {
       e[tot].u = u; e[tot].v = v; e[tot].w = w; e[tot].c = c;
       e[tot].next = head[u]; head[u] = tot++;
    }
    
    void Dij()
    {
       priority_queue<State>q;
       State sta;
       int res = INF ;
       memset(d,0x3f,sizeof(d));
       while(!q.empty()) q.pop();
       sta.d = 0;
       sta.n = 1;
       sta.c = 0;
       q.push(sta);
       while(!q.empty())
       {
          State x,y;
          int u,v,w,d,c;
    
          x = q.top(); q.pop();
          u = x.n; d = x.d;
    
          if(u == n)
          {
             res = x.d;
             break;
          }
    
          for(int k=head[u]; k!=-1; k=e[k].next)
          {
             v = e[k].v; w = e[k].w; c = e[k].c;
             if(x.c + c <= cost) //在花费允许的范围内可以去到这个点
             {
                y.n = v;  y.d = d + w;  y.c = x.c + c;
                q.push(y);
             }
          }
       }
       if(res == INF) printf("-1\n");
       else           printf("%d\n",res);
    }
    
    int main()
    {
       scanf("%d%d%d",&cost,&n,&m);
       memset(head,-1,sizeof(head));
       tot = 0;
       while(m--)
       {
          int u,v,w,c;
          scanf("%d%d%d%d",&u,&v,&w,&c);
          add(u,v,w,c);
       }
       Dij();
       return 0;
    }

    2.普通队列+spfa(或者说是直接的一个bfs,时间次之)

       定义一个状态d[i][j]表示从1号顶点走到i号顶点花费了j个点数能走出的最短路。那么状态之间的转移是不能想的,即便是加了点数这个限制条件也不难(不就是判断的时候多判断一下)。然后很快写出了一个代码,提交,TLE。然后怎么改都是TLE,最后就去思考是怎么TLE的

    先放上这个TLE的代码

    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <queue>
    using namespace std;
    #define N 110
    #define M 10010
    #define INF 0x3f3f3f3f
    
    struct State
    {
       int n,c,d;
    };
    struct edge
    {
       int u,v,w,c,next;
    };
    
    typedef struct State State;
    typedef struct edge edge;
    
    int n,m,cost,tot;
    int d[N][M];
    int head[N];
    edge e[M];
    bool inq[N];
    
    void add(int u ,int v ,int w ,int c)
    {
       e[tot].u = u;  e[tot].v = v;  e[tot].w = w;  e[tot].c = c;
       e[tot].next = head[u];  head[u] = tot++;
    }
    
    void spfa()
    {
       int res;
       queue<State>q;
       State tmp;
    
       while(!q.empty()) q.pop();
       memset(d,0x3f,sizeof(d));
       memset(inq,false,sizeof(inq));
       d[1][0] = 0; inq[1] = true;
       tmp.n = 1; tmp.c = 0; tmp.d = 0;
       q.push(tmp);
    
       res = INF;
       while(!q.empty())
       {
          State x ,y;
          x = q.front();
          q.pop();
          inq[x.n] = false;
          if(x.n == n && x.d < res)
             res = x.d;
    
          for(int k = head[x.n]; k!=-1; k=e[k].next)
          {
             int v = e[k].v;
             int w = e[k].w;
             int c = e[k].c;
             if(x.c +  c <= cost)
             {
                int cc = x.c + c;
                if( x.d + w < d[v][cc])
                {
                   d[v][cc] = x.d + w;
                   if(!inq[v])
                   {
                      y.n = v;
                      y.c = cc;
                      y.d = d[v][cc];
                      q.push(y);
                      inq[v] = true;
                   }
                }
             }
          }
       }
       if(res == INF) printf("-1\n");
       else           printf("%d\n",res);
    }
    
    int main()
    {
       scanf("%d%d%d",&cost,&n,&m);
       memset(head,-1,sizeof(head));
       tot = 0;
       while(m--)
       {
          int u,v,w,c;
          scanf("%d%d%d%d",&u,&v,&w,&c);
          add(u,v,w,c);
       }
       spfa();
       return 0;
    }

    在一个BFS搜索中,TLE的原因,一般就是因为没有剪枝,即重复的状态搜了太多次,那么就想了,怎么剪枝呢?怎么判断重复状态的搜索的?代码中已经有inq[i][j]这样的标记数组了啊,但是可以想到这样的记录其实意义不大,一是状态数太多(n*m),再者是有重边的关系,更新可以很频繁,状态出队入队的次数可以很多。

    所以我们可以改变一下更新的策略和队列中的元素的定义,每得到一个点,彻底更新所有点数对应的状态,我们只看一个点是否被修改了最短路径值,是的话才能入队,这样的限制,大大减少了元素入队出队的次数

    这个是AC的代码

    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    #define N 110
    #define M 10010
    #define INF 0x3f3f3f3f
    
    int head[N];
    struct edge
    {
        int u,v,w,c,next;
    }e[M];
    int cost,n,m,tot;
    int d[N][M];
    bool inq[N];
    
    void add(int u , int v , int w , int c)
    {
       e[tot].u = u; e[tot].v = v; e[tot].w = w; e[tot].c = c;
       e[tot].next = head[u]; head[u] = tot++;
    }
    
    void bfs()
    {
       int res;
       queue<int>q;
    
       memset(inq,false,sizeof(inq));
       memset(d,0x3f,sizeof(d));
       for(int i=0; i<=cost; i++) d[1][i] = 0;
       while(!q.empty()) q.pop();
       inq[1] = true; q.push(1);
    
       while(!q.empty())
       {
          int u = q.front();
          q.pop();
          inq[u] = false;
    
          for(int k=head[u]; k!=-1; k=e[k].next)
          {
             int v = e[k].v;
             int w = e[k].w;
             int c = e[k].c;
             for(int j=c; j<=cost; j++) //彻底更新所有的状态
             {
                if(d[u][j-c] + w < d[v][j])
                {
                   d[v][j] = d[u][j-c] + w;
                   if(!inq[v])
                   {
                      q.push(v);
                      inq[v] = true;
                   }
                }
             }
          }
       }
       res = INF;
       for(int i=0; i<=cost; i++)
          if(d[n][i] < res)
             res = d[n][i];
       if(res == INF) printf("-1\n");
       else           printf("%d\n",res);
    }
    
    int main()
    {
        scanf("%d%d%d",&cost,&n,&m);
        memset(head,-1,sizeof(head));
        tot = 0;
        while(m--)
        {
            int u,v,w,c;
            scanf("%d%d%d%d",&u,&v,&w,&c);
            add(u,v,w,c);
        }
        bfs();
        return 0;
    }

    3.DP,记忆化搜索,需要逆向建图,容易写(最慢)

      状态的定义和上面的spfa是一样,d[i][j]表示从1到i点花费j点数走出的最短路,那么这个东西就很容易引导我们想到DP,而确实是这样的,但是问题是,要DP,要写递归式,需要的是反边,即要知道点v的信息,是由点u得到的(有向边u--->v),所以建图的时候逆向建图,剩下的记忆化搜索,是很容易写出来的

    #include <cstdio>
    #include <cstring>
    #define N 110
    #define M 10010
    #define INF 0x3f3f3f3f
    
    int head[N];
    struct edge
    {
        int u,v,w,c,next;
    }e[M];
    int cost,n,m,tot;
    int d[N][M];
    
    void add(int u , int v , int w , int c)
    {
       e[tot].u = u; e[tot].v = v; e[tot].w = w; e[tot].c = c;
       e[tot].next = head[u]; head[u] = tot++;
    }
    
    void dfs(int u ,int c)
    {
        if(d[u][c] != -1) return ;
        d[u][c] = INF;
        for(int k=head[u]; k!=-1; k=e[k].next)
        {
            int v = e[k].v;
            int w = e[k].w;
            int cc = e[k].c;
            if(c - cc >= 0)
            {
                dfs(v,c-cc);
                if(d[v][c-cc]+w < d[u][c])
                    d[u][c] = d[v][c-cc]+w;
            }
        }
    }
    
    int main()
    {
        scanf("%d%d%d",&cost,&n,&m);
        memset(head,-1,sizeof(head));
        tot = 0;
        while(m--)
        {
            int u,v,w,c;
            scanf("%d%d%d%d",&u,&v,&w,&c);
            add(v,u,w,c); //逆向建图,为了逆向DP
        }
        memset(d,-1,sizeof(d));
        for(int i=0; i<=cost; i++) d[1][i] = 0;
        int res = INF;
        for(int i=0; i<=cost; i++)
        {
            dfs(n,i);
            if(d[n][i] < res) 
                res = d[n][i];
        }
        if(res == INF) printf("-1\n");
        else           printf("%d\n",res);
    }
  • 相关阅读:
    [手把手]VMware 16 pro 装 Windows11专业版并激活
    [HTML] 做个空壳网页练手(菜鸡的自我信息完善
    从零玩HTML的一天
    [总结]C++ 之 向量vector
    [递归专题打卡]2021 6.30-7.2
    初学Socket笔记
    对java是编译型语言还是解释型语言的讨论
    PHP CURL POST 请求设置 Content-Type (指定Content-Type)
    webpack 报错 [webpack-cli] Unable to load '@webpack-cli/serve' command
    Vue cli 创建项目模板 / npm run build 打包后资源引用问题
  • 原文地址:https://www.cnblogs.com/scau20110726/p/3050178.html
Copyright © 2011-2022 走看看