[状态压缩DP+三进制] HDU 3001

动态规划方法解旅行商问题(TSP Traveling Salesperson Problem)

http://blog.csdn.net/hellonerd/article/details/50920234


首先是TSP的状态转移方程:

dp[A+vk][vk] = min ( dp[A][vj] + edge[vj][vk] ) 

A是已经经过了的节点的集合,vj是从起点开始经过A里面所有的节点后到达的最末

dp[A][vj]就储存了这段路径的距离

 从没走过的节点里选一个加入路径,取最短

最后A集合包含所有节点的时候就是最短的距离了


状态压缩:

用0、1代表所有的的点是走过还是没走过(是否包含在A里面)


二进制的码(再也不要百度了。。。。)

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

using namespace std;

#define INF 0x3f3f3f3f

const int n = 10;
int dp[ 1 << n ][ n ];
int edge[ 1 << n ][ n ];

void tsp () {

        memset ( dp, INF, sizeof ( dp ) );
        dp[ 1 ][ 0 ] = 0; //起点为0,起点的状态是1( 0位是1 )

        // i,k不需要在经过起点了
        for ( int i = 1; i < ( 1 << n ); ++i ) { // 2^n种状态
                for ( int j = 0; j < n; ++j ) {
                        if ( dp[ i ][ j ] == INF )
                                continue;

                        //从i到k + 从k到j
                        for ( int k = 1; k < n; ++k ) {
                                // k已经走过了
                                if ( i & ( 1 << k ) )
                                        continue;

                                // 记录新的状态i,添加了k位为1
                                int nex = ( i | ( 1 << k ) );
                                dp[ nex ][ k ] =
                                    min ( dp[ nex ][ k ], dp[ i ][ j ] + edge[ j ][ k ] );
                        }
                }
        }

        int ans = INF;
        for ( int i = 0; i < n; ++i )
                ans = min ( ans, dp[ ( 1 << n ) - 1 ][ i ] + edge[ i ][ 0 ] );
        printf ( "%d\n", ans );
}



本题每个节点可以走两次

所以要用三进制

而且有多个起点


/**
hdu3001  状态压缩dp+三进制。
每个顶点经过最多2次,也就是说有0,1,2三总状态,我们状态转移的时候要用三进制。另外起点任意,
所以dp[bit[i]][i]=0,剩下的初始化为INF。状态转移方程为:dp[nex][k] = min(dp[nex][k], dp[i][j] +
edge[j][k]); 其中( next = i + bit[k])。
三进制没有对应的移位操作因此我们要模拟实现:首先把0~3^10之间的所有数都用一个数组ternary[i][j]表示出来,
1~10位分别代表i化为三进制后每个位上对应的值(第一位对应个位,第10位对应最高位),然后状态转移就可以了
*/

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

#define INF 0x3f3f3f3f

using namespace std;

int n, m;
int edge[ 12 ][ 12 ];
int dp[ 60000 ][ 12 ]; //当前状态哪些走过了,走到了的行数位置
int bit[ 11 ] = {1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049};
int ternary[ 60000 ][ 12 ]; //每个数的三进制

void make_trb () {
        for ( int i = 0; i < bit[ 10 ]; ++i ) {
                int b = i;
                for ( int j = 0; j < 10; ++j ) {
                        ternary[ i ][ j ] = b % 3;
                        b /= 3;
                }
        }
}

int tsp () {

        int ans = INF;

        memset ( dp, INF, sizeof ( dp ) );
        // dp[ 1 ][ 0 ] = 0; //起点为0,初始状态为2<<0 = 1
        for ( int j = 0; j < n; ++j ) //每个节点都是初始状态
                dp[ bit[ j ] ][ j ] = 0;

        int flag;
        // for ( int i = 1; i < ( 1 << n ); ++i ) { // 2^n种状态
        for ( int i = 0; i < bit[ n ]; ++i ) {
                flag = 1; //代表所有点都走过
                for ( int j = 0; j < n; ++j ) {

                        //状态i的三进制第j位是0,还存在没有走过的点
                        if ( ternary[ i ][ j ] == 0 )
                                flag = 0;

                        //没有i -- j 的通路
                        if ( dp[ i ][ j ] == INF )
                                continue;

                        //从i到k + 从k到j
                        for ( int k = 0; k < n; ++k ) {
                                // k已经走过了,( 状态i下的第k位>1 )
                                // if ( i & ( 1 << k ) )
                                // k就是j,k走过两次了,边不存在
                                if ( j == k || ternary[ i ][ k ] >= 2 || edge[ k ][ j ] == -1 )
                                        continue;

                                // 记录新的状态i,添加了k位为1
                                // int nex = ( i | ( 1 << k ) );
                                int nex = i + bit[ k ];
                                dp[ nex ][ k ] =
                                    min ( dp[ nex ][ k ], dp[ i ][ j ] + edge[ j ][ k ] );
                        }
                }

                if ( flag ) {
                        for ( int j = 0; j < n; ++j )
                                ans = min ( ans, dp[ i ][ j ] );
                }
        }
        return ans;
}

int main () {
        make_trb ();

        while ( ~scanf ( "%d%d", &n, &m ) ) {
                memset ( edge, -1, sizeof ( edge ) );
                for ( int i = 0; i < m; ++i ) {
                        int a, b, c;
                        scanf ( "%d%d%d", &a, &b, &c );
                        --a, --b;
                        if ( edge[ b ][ a ] == -1 )
                                edge[ b ][ a ] = edge[ a ][ b ] = c;
                        else
                                edge[ b ][ a ] = edge[ a ][ b ] = min ( edge[ a ][ b ], c );
                }

                int sol = tsp ();
                if ( sol == INF )
                        sol = -1;
                printf ( "%d\n", sol );
        }
        return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值