码迷,mamicode.com
首页 > 其他好文 > 详细

源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp

时间:2018-06-08 00:37:58      阅读:170      评论:0      收藏:0      [点我收藏+]

标签:memset   重要   set   clu   就是   程序   name   names   体会   

连接:http://bailian.openjudge.cn/practice/4124

题意:从1到n走过所有点恰好一次最短时间。乱搞的话会完美的超时(阶乘级别的复杂度,虽然范围很小,但是也足够超时了)。

思路:先想一个不太成熟的思路。用dp[s][j]表示。s记录的是每个点是否被走过的状态。而dp[s][j]表示的是从1走到j状态所用的最小时间。这样的思路成不成立呢?首先,考虑初始值。开始是在1号点,那么dp[1][1]自然就是0了,其他就是max;另外,题面说只要遍历每一个点,而于顺序的话,并没有要求,也就是说,通过任何一种演变方式到达该状态的方式都是等价的,满足了动态规划无后效性的要求。同时,对于每一个时刻,当前位置存储的值都是当前最优解,既问题具有最优子结构性质。同时,对于每一个演变,我们可以在dp[s][j]的基础上,推出当前状态的值可以通过上一步演变就到达的状态进行更新,这也就是所谓“人人为我”的过程。dp方程也好想,既:

dp[i][k] = min(dp[i][k],dp[i_pre][k_pre]+G[i_pre][i]);

接下来就是比较重要的问题了:如何表示这些状态,以及进行状态之间的计算呢?

用16个bool?太麻烦了!换一种思路:因为只有16个数,将他们编成二进制编码101010101010100……每一位代表当前位置所代表的点是否被走过,这样的话,只需要2^16个无符号shortint(实际用int)就可以表示所有可能的状态啦。

第一个问题是解决了,可如何进行状态间的变换呢?请把c语言程序设计翻到xxx页,有关位运算的章节:

&, 这个东西叫按位与,既每一位依次比较一样就是1,不一样就是0。平时常用的判断奇偶性的n&1就是最简单的应用。

|,这东西叫按位或,键位有点怪,一般在enter附近,意思是每一位依次比较有1就是1,全0就是0

^,按位异或,也是中文输入法下省略号的打法。官方的话是相同为0,不同为1,我的理解就是不带进位的加法。

~,取反,在tab上面,int下的话就是~x = -x-1 最常用的那个while(~scanf)用的就是这个原理(~0 = -0-1 = -1 = EOF)。

<< >> 左移右移 不多说,乘2除2

 

一些比较清奇的用法

从低位到高位,取n的第m位

return (n >> (m-1)) & 1;
从低位到高位.将n的第m位置1
return n | (1 << (m-1));
从低位到高位,将n的第m位置0

return n & ~(1 << (m-1));

(x&-x)只保留最低位的1

具体用法的话读代码,体会一下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

void read(){
#ifndef ONLINE_JUDGE
    freopen("D:\\fengyu\\Jiang_C\\.vscode\\in.txt","r",stdin);
    freopen("D:\\fengyu\\Jiang_C\\.vscode\\out.txt","w",stdout);
#endif
}
int G[20][20];
int dp[20][(1<<16)+5];
int main() { read();
    int n;
    while (cin >> n) {
        memset(dp,0x3f,sizeof(dp));
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                cin >> G[i][j];
            }
        }
        dp[1][1] = 0;
        int ans = (1<<n)-1;
        for (int k = 1; k <= ans; k++) {
            for (int i = 1,_i = 1; i <= n; i++,_i<<=1) {
                if (k&_i)
                for (int j = 1, _j = 1; j <= n; j++,_j<<=1) {
                    if (i!=j && k&_j)
                        dp[i][k] = min(dp[i][k],dp[j][k^_i]+G[j][i]);
                }
            }
        }
        cout << dp[n][ans] << endl;
    }
    return 0;
}

 

源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp

标签:memset   重要   set   clu   就是   程序   name   names   体会   

原文地址:https://www.cnblogs.com/fengyuzhicheng/p/9153254.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!