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);
}

 

posted @ 2013-04-28 23:20  Titanium  阅读(2930)  评论(0编辑  收藏  举报