POJ 1661Help Jimmy(逆向DP Or 记忆化搜索 Or 最短路径)

Help Jimmy

"Help Jimmy" 是在下图所示的场景上完成的游戏。 
POJ  1661Help Jimmy(逆向DP Or 记忆化搜索 Or 最短路径)

场景中包括多个长度和高度各不相同的*台。地面是最低的*台,高度为零,长度无限。 

Jimmy老鼠在时刻0从高于所有*台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个*台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1米/秒。当Jimmy跑到*台的边缘时,开始继续下落。Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。 

设计一个程序,计算Jimmy到底地面时可能的最早时间。 
Input
第一行是测试数据的组数t(0 <= t <= 20)。每组测试数据的第一行是四个整数N,X,Y,MAX,用空格分隔。N是*台的数目(不包括地面),X和Y是Jimmy开始下落的位置的横竖坐标,MAX是一次下落的最大高度。接下来的N行每行描述一个*台,包括三个整数,X1[i],X2[i]和H[i]。H[i]表示*台的高度,X1[i]和X2[i]表示*台左右端点的横坐标。1 <= N <= 1000,-20000 <= X, X1[i], X2[i] <= 20000,0 < H[i] < Y <= 20000(i = 1..N)。所有坐标的单位都是米。 

Jimmy的大小和*台的厚度均忽略不计。如果Jimmy恰好落在某个*台的边缘,被视为落在*台上。所有的*台均不重叠或相连。测试数据保证问题一定有解。 
Output
对输入的每组测试数据,输出一个整数,Jimmy到底地面时可能的最早时间。
Sample Input
1
3 8 17 20
0 10 8
0 10 13
4 14 3
Sample Output
23


这道题还是和here很像的!!!


补充DP和记忆化搜索:here


 此题目的“子问题”是什么呢?
n Jimmy 跳到一块板上后,可以有两种选择,向左走或向右走。走到左端和走到右端所需的时间,容易算出。
n 如果我们能知道,以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左走还是向右走,就很容选择了。
n 因此,整个问题就被分解成两个子问题,即Jimmy 所在位置下方第一块板左端为起点到地面的最短时间,和右端为起点到地面的最短时间。这两个子问题在形式上和原问题是完全一致的。

当Jimmy落在一个*台上后有两种选择(向左走或向右走),而Jimmy走到*台左边和右边的时间很容易计算,如果我们得到了以*台左边为起点及以*台右边为起点到地面的最短时间,那么选择往左走还是往右走就很容易了。这样,原问题就分解为两个子问题这两个子问题和原问题的形式是一致的了,也就找到了“状态”dp[i][j], j = 0, 1 (dp[i][0]表示以i号*台左边为起点到地面的最短时间,dp[i][1]]表示以i号*台右边为起点到地面的最短时间),而“状态转移方程”如下:

dp[i][0] = H[i] - H[m] + Min (dp[m][0] + X1[i] - X1[m], dp[m][1] + X2[m] - X1[i]);  m为i左边下面的*台的编号

dp[i][1] = H[i] - H[m] + Min (dp[m][0] + X2[i] - X1[m], dp[m][1] + X2[m] - X2[i]);  m为i右边下面的*台的编号


参考网址:here

Bottom-Up(DP)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    #define INF 0x3f3f3f3f

    ///定义每个*台的左端点x1,右端点x2及高度h
    struct step
    {
        int x1,x2,h;
        friend bool operator < (step a,step b)///友元函数运算符,"<"运算符重载,以便运用sort函数
        {  return a.h<b.h; }
    }plat[1005];
    ///同样是逆向DP,因为起点固定
    
    int dp[1005][2];///dp[i][j]表示第i个*台后的最小时间,"j=0"表示到达左端点,下一步为左下方

    int main()
    {
        ///共有t组测试数据
        int T;
        scanf("%d",&T);
        while(T--)
        {
            int N,X,Y,MAX;///N:*台数目;(X,Y):开始下落位置坐标;MAX:一次下落最大高度

            scanf("%d%d%d%d",&N,&X,&Y,&MAX);

            ///输入每个*台的属性
            for(int i=1;i<=N;i++)
              scanf("%d%d%d",&plat[i].x1,&plat[i].x2,&plat[i].h);
            sort(plat+1,plat+N+1);///按高度从小到大

            plat[N+1].x1=plat[N+1].x2=X , plat[N+1].h=Y;///把出发点也当做一个*台
            plat[0].x1=-INF, plat[0].x2=INF, plat[0].h=0; ///给地面这一*台初始化
            
            memset(dp,0,sizeof(dp));

            ///从第1层开始
            for(int i=1;i<=N+1;i++)
            {
                int lflag=0,rflag=0;///记录下一步能否向左、右走
                ///下一步向左走
                for(int j=i-1;j>=0&&plat[i].h-plat[j].h<=MAX;j--)
                {
                    if(plat[i].x1>=plat[j].x1&&plat[i].x1<=plat[j].x2)
                    {
                        lflag=1;
                        if(j==0) ///地面
                            dp[i][0]=plat[i].h;
                        else
                            dp[i][0]=plat[i].h-plat[j].h+min(dp[j][0]+plat[i].x1-plat[j].x1,dp[j][1]+plat[j].x2-plat[i].x1);
                        break;
                    }
                }
                if(lflag==0)
                    dp[i][0]=INF;

                ///下一步向右走
                for(int j=i-1;j>=0&&plat[i].h-plat[j].h<=MAX;j--)
                {
                    if(plat[i].x2>=plat[j].x1&&plat[i].x2<=plat[j].x2)
                    {
                        rflag=1;
                        if(j==0)
                            dp[i][1]=plat[i].h;
                        else
                            dp[i][1]=plat[i].h-plat[j].h+min(dp[j][0]+plat[i].x2-plat[j].x1,dp[j][1]+plat[j].x2-plat[i].x2);
                        break;
                    }
                }
                if(rflag==0)
                    dp[i][1]=INF;
            }
            printf("%d
",min(dp[N+1][0],dp[N+1][1]));
        }
        return 0;
    }

Top-Down(记忆化搜索) 参考网址:here

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
struct point
{
    int x1,x2,h;
} plat[1010];
bool cmp(point a,point b)
{
    return a.h>b.h;
}
int N,X,Y,MAX;
int  num[1010][2];


int dfs(int i,int a,int x)
{
    if(num[i][a]!=-1)    return num[i][a];
    int left=100000000,right=100000000;


    bool flag=true;  ///
    for(int j=i+1; j<=N; j++)
    {
        if(plat[i].h-plat[j].h>MAX)
        {
            flag=false;
            break;
        }
        if(x>=plat[j].x1&&x<=plat[j].x2)
        {
            left=dfs(j,0,plat[j].x1)+plat[i].h-plat[j].h+x-plat[j].x1;
            right=dfs(j,1,plat[j].x2)+plat[i].h-plat[j].h+plat[j].x2-x;
            flag=false;
            break;
        }
    }
    num[i][a]=min(left,right);
    if(flag&&plat[i].h<=MAX)
        num[i][a]=plat[i].h;
    return num[i][a];


}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        memset(num,-1,sizeof num);
        scanf("%d%d%d%d",&N,&X,&Y,&MAX);


        plat[0].x1=X, plat[0].x2=X, plat[0].h=Y;///将终点作为一个*台
        for(int i=1; i<=N; i++)
            scanf("%d%d%d",&plat[i].x1,&plat[i].x2,&plat[i].h);
        sort(plat,plat+N+1,cmp);
        printf("%d
",dfs(0,1,X));
    }
    return 0;
}
/*
1
3 8 17 20
0 10 13
0 10 8
4 14 3
*/






好神奇的转化啊!转成图来想,巧妙!!!不过代码写起来就比搜索和DP要麻烦些了

思路:我是转化成求最短路来解的。
     将每个*台看作两个点,即左端点和右端点,然后将符合条件的两点相连,边长即为两点之间的垂直距离和水*距离。
     将jimmy起始的地点看作顶点0,而地面看作顶点2*N+1,这样就是求0到2*N+1的单源最短路径,用dijkstra就可以搞定。

有几个要注意的地方:
  1.一开始做的时候,没仔细想,认为只要两个*台之间符合条件,就建立边的关系。
   忽略了一个*台下方最多只能有两个*台(即左端点下方一个,右端点下方一个)。也就是说一个点最多只能与一个点相连。
  2.jimmy有可能可以直接落到地上
  3.能与地面相连的点,必须保证它的下方没有阻隔的*台

参考网址:here

#include <iostream>
#include <queue>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <string.h>

using namespace std;
const int maxn=1000*2+5;
const int INF=0x3f3f3f3f;
int N,X,Y,MAX;
long long dis[maxn];
int vis[maxn];  
int head[maxn];
int tot;
long long ans;  
struct Line {
    int l,r,h;
    bool operator<(const Line tmp)const {
        return h>tmp.h;
    }
} line[maxn];
struct Node {
    int u;
    long long dis;
    bool operator<(const Node tmp)const {
        return dis>tmp.dis;
    }
};

struct Edge {
    int to,next;
    int length;
} edge[maxn*maxn];

void add(int i,int j,int dis) {
    edge[tot].next=head[i];
    edge[tot].to=j;
    edge[tot].length=dis;
    head[i]=tot++;
}
void dijkstra(int s) {
    for(int i=0; i<maxn; i++) {
        dis[i]=INF;
        vis[i]=0;
    }
    priority_queue<Node>q;
    Node t;
    t.u=s;
    t.dis=0;
    dis[s]=0;
    q.push(t);
    int v,u;
    while(!q.empty()) {
        t=q.top();
        q.pop();
        u=t.u;
        vis[u]=1;

        if(u==2*N+1){
            ans=t.dis;
            break;
        }

        for(int k=head[u]; k!=-1; k=edge[k].next) {
            v=edge[k].to;
            if(!vis[v] && dis[u]+edge[k].length<dis[v]) {
                dis[v]=dis[u]+edge[k].length;
                t.u=v;
                t.dis=dis[v];
                q.push(t);
            }


        }
    }
}
int main() {
    int t;
    scanf("%d",&t);
    while(t--) {
        memset(head,-1,sizeof(head));
        tot=0;
        scanf("%d%d%d%d",&N,&X,&Y,&MAX);
        for(int i=1; i<=N; i++) {
            scanf("%d%d%d",&line[i].l,&line[i].r,&line[i].h);
        }
        sort(line+1,line+N+1);
        int ldis,rdis;
        for(int i=1; i<=N; i++) {
            //这里要考虑,不是所有在i*台下方符合的就可以建立边的关系
            //而只能与它最*的*台才行。
            //例如*台1,2,3,并且2、3都在1的下方,且满足条件。但1和3不能相连,因为中间隔着2,所以只能1和2相连
            int cnt1=0,cnt2=0;
            for(int j=i+1; j<=N; j++) {
                if(line[i].h-line[j].h>=0 && line[i].h-line[j].h<=MAX) {
                    if(line[j].l<=line[i].l && line[i].l<=line[j].r) {
                        cnt1++;
                        //左端下方第一个符合要求的
                        if(cnt1==1) {
                            ldis=line[i].h-line[j].h+line[i].l-line[j].l;
                            add(i*2-1,j*2-1,ldis);

                            rdis=line[i].h-line[j].h+line[j].r-line[i].l;
                            add(i*2-1,j*2,rdis);
                        }
                    }
                    if(line[j].l<=line[i].r && line[i].r<=line[j].r) {
                        cnt2++;
                        //右端点下方第一个符合要求的
                        if(cnt2==1) {
                            ldis=line[i].h-line[j].h+line[i].r-line[j].l;
                            add(i*2,j*2-1,ldis);

                            rdis=line[i].h-line[j].h+line[j].r-line[i].r;
                            add(i*2,j*2,rdis);
                        }
                    }
                }
            }
            //若要与地面连接,则必须该*台下方没有其它阻隔的*台。
            //如果该*台左端点下方没有其它*台阻隔,且满足条件,则与地面相连
            if(!cnt1 && line[i].h<=MAX) {
                add(i*2-1,2*N+1,line[i].h);
            }
            //如果该*台右端点下方没有其它*台阻隔,且满足条件,则与地面相连
            if(!cnt2 && line[i].h<=MAX) {
                add(i*2,2*N+1,line[i].h);
            }
        }
        //只能有一个*台与源点连接
        int cnt=0;
        for(int j=1; j<=N; j++) {
            if(line[j].l<=X && X<=line[j].r && Y-line[j].h>=0 && Y-line[j].h<=MAX) {
                cnt++;
                //第一个符合要求的*台
                if(cnt==1) {
                    ldis=Y-line[j].h+X-line[j].l;
                    add(0,j*2-1,ldis);

                    rdis=Y-line[j].h+line[j].r-X;
                    add(0,j*2,rdis);
                }
            }
        }
        //没有*台在jimmy的下方,也就是jimmy可以直接到达地面。。。之前都忽略了额
        if(cnt==0) {
            add(0,2*N+1,Y);
        }
        dijkstra(0);
        printf("%I64d
",ans);
    }
    return 0;
}