HDU-1619 Unidirectional TSP dp

大致题意:
从左到右寻找最短路径,按字典序输出路径以及路径长。

思路:
从右往左每两列一看,计算局部最优解,记录在数组dp中;

例:
5行6列的Map
3 4 1 2 8 6
6 1 8 2 7 4
5 9 3 9 9 5
8 4 1 3 2 6
3 7 2 8 6 4

对应dp
一:
0 0 0 0 0 6
0 0 0 0 0 4
0 0 0 0 0 5
0 0 0 0 0 6
0 0 0 0 0 4
二:
0 0 0 0 12 6
0 0 0 0 11 4
0 0 0 0 13 5
0 0 0 0 6 6
0 0 0 0 10 4
这里的12是4 6 4三者最小(4)加上Map对应位置元素值(8)
这里的11是6 4 5三者最小(4)加上Map对应位置元素值(7)

这里的10是6 4 6三者最小(4)加上Map对应位置元素值(6)

不难看出,dp中存储每两列的局部最优解,然后我们可以把这两列看成一列,同样地做法往前递推,即可得到整个Map的最优解,也就是dp第一列中最小的元素

写出状态转移方程:
dp[i][j] = mm + map[i][j]

mm为dp[i][j]向右所指的三个子元素的最小值
一般情况为dp[i-1][j+1], dp[i][j+1], dp[i+1][j+1]
特别考虑i = 1和i = m的情况

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define MAX 102
using namespace std;

int map[MAX][MAX];
int dp[MAX][MAX];

int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif


    int m, n;
    int i, j;
    int mm, ans, route;
    //mm记录dp向右所指三个子元素的最小值
    //ans记录最短路径长
    //route标记路径

    while(~scanf("%d%d", &m, &n))
    {
        memset(map, 0, sizeof(map));
        memset(dp, 0, sizeof(dp));

        //输入map
        for(i = 1; i <= m; i++)
            for(j = 1; j <= n; j++)
                scanf("%d", &map[i][j]);

        //计算mm, dp, 从右往左按列递推
        for(j = n; j >= 1; j--)
            for(i = 1; i <= m; i++)
            {
                if(i == 1)
                    mm = min( min(dp[m][j+1], dp[1][j+1]), dp[2][j+1] );

                else if(i == m)
                    mm = min( min(dp[m-1][j+1], dp[m][j+1]), dp[1][j+1] );

                else
                    mm = min( min(dp[i-1][j+1], dp[i][j+1]), dp[i+1][j+1] );

            //状态转移方程
            dp[i][j] = mm + map[i][j];
            }

            //1<<30意思是开了一个很大的数
            ans = 1<<30;

        //找dp第一列最小元素,即最短路径长,并标记位置route
        for(i = 1; i <= m; i++)
            if(dp[i][1] < ans)
            {
                ans = dp[i][1];
                route = i;
            }

            printf("%d", route);

        //寻找子route(从左往右根据dp三个子元素的最小确定)
        for(j = 2; j <= n; j++)
        {

            if(route == 1)
            {
                if(dp[1][j] <= dp[m][j] && dp[1][j] <= dp[2][j])
                {
                    route = 1;
                    printf(" %d", route);
                }
                else if(dp[2][j] <= dp[m][j] && dp[2][j] <= dp[1][j])
                {
                    route = 2;
                    printf(" %d", route);
                }
                else
                {
                    route = m;
                    printf(" %d", route);
                }
            }

            else if(route == m)
            {
                if(dp[1][j] <= dp[m][j] && dp[1][j] <= dp[m-1][j])
                {
                    route = 1;
                    printf(" %d", route);
                }
                else if(dp[m-1][j] <= dp[m][j] && dp[m-1][j] <= dp[1][j])
                {
                    route = m-1;
                    printf(" %d", route);
                }
                else
                {
                    route = m;
                    printf(" %d", route);
                }
            }

            else
                if(dp[route-1][j] <= dp[route][j] && dp[route-1][j] <= dp[route+1][j])
                {
                    route = route-1;
                    printf(" %d", route);
                }
                else if(dp[route][j] <= dp[route-1][j] && dp[route][j] <= dp[route+1][j])
                    printf(" %d", route);
                else
                {
                    route = route+1;
                    printf(" %d", route);
                }
        }
        printf("\n%d\n", ans);

    }

    return 0;
}

缺点及改进:
代码段过于冗长,最好把各个重复的功能段独立成功能函数,简化代码。
最后输出路径的方法不太好,可以在计算dp的时候直接计算路径。
例:http://blog.csdn.net/z309241990/article/details/8617785
不过我感觉效率上面不差太多,我这样写还更好懂一些。

小结:
dp算法,关键代码就一两行,然而要弄懂整个流程并不容易,这题还算一道简单dp,希望能再熟练学习,更好运用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值