HDU 5418 状压DP + 最短路

HDU 5418

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=5418

题意:

给一个图,n个点,m条边,保证1到除1外所有点有边。

可以重复经过城市,问从城市1出发,所有城市至少经过一遍最后回到1的最小代价。

思路:

比赛的时候想过状压,但是因为图可能是不完全连通的,也就是从点u到点v要经过其他点,这时候这个状态没办法加上去。

题解就是状压。dp[S][i]S表示当前已经访问过哪些点,i表示最后到达的点(不加这个状态无法出结果)。Floyd算出任意两点最短距离,然后就状压dp

证明正确性:假设当前要更新的状态是Suv需要经过temp点。

1)tempS中。那么没有重复走。

2)temp不在S中。对于状态S | (1 << (temp - 1)),可以用S更新,也可以用{S + u} + {v}更新。通过DP把中间重复走的路剪去了。

需要特判n=1~

源码:

#include <cstdio>

#include <cmath>

#include <cstring>

#include <cstdlib>

#include <algorithm>

#include <iostream>

#include <vector>

#define inf (1000000000)

#define gmin(a,b) ((a) < (b) ? (a) : (b))

#define gmax(a,b) ((a) > (b) ? (a) : (b))

const int MAXN = 20;

int gra[MAXN][MAXN];

int dp[1<<20][MAXN], n;

void floyd()

{

    for(int k = 1 ; k <= n ; k++){

        for(int i = 1 ; i <= n ; i++){

            for(int j = 1 ; j <= n ; j++)

                gra[i][j] = gmin(gra[i][j], gra[i][k] + gra[k][j]);

        }

    }

}

int main()

{

    int t;

    scanf("%d", &t);

    while(t--){

        int m;

        scanf("%d%d", &n, &m);

        for(int i = 1 ; i <= n ; i++)

            for(int j = 1 ; j <= n ; j++)

                gra[i][j] = inf;

        for(int i = 0 ; i < m ; i++){

            int u, v, w;

            scanf("%d%d%d", &u, &v, &w);

            gra[u][v] = gmin(gra[u][v], w);

            gra[v][u] = gmin(gra[v][u], w);

        }

        floyd();

        for(int i = 0 ; i < (1 << n) ; i++)

            for(int j = 1 ; j <= n ; j++)

                dp[i][j] = inf;

        for(int i = 1 ; i <= n ; i++)

            dp[1 << (i - 1)][i] = gra[1][i];

        for(int s = 1 ; s < (1 << n) ; s++){

            for(int i = 1 ; i <= n ; i++){

                if(s & (1 << (i - 1))){

                    int ts = s - (1 << (i - 1));

                    if(ts == 0)

                        continue;

                    int lin = inf;

                    for(int j = 1 ; j <= n ; j++){

                        if(ts & (1 << (j - 1))){

                            lin = gmin(lin, dp[ts][j] + gra[j][i]);

                        }

                    }

                    dp[s][i] = gmin(dp[s][i], lin);

                }

            }

        }

//        printf("gra\n");

//        for(int i = 1 ; i <= n ; i++){

//            for(int j = 1 ; j <= n ; j++)

//                printf("%d ", gra[i][j]);

//            printf("\n");

//        }

//        printf("gra\n");

//        printf("dp\n");

//        for(int i = 1 ; i <= ((1 << n) - 1) ; i++){

//            printf("In base 2 i = ");

//            for(int j = 1 ; j <= n ; j++){

//                if(i & (1 << (j - 1)))

//                    printf("1");

//                else

//                    printf("0");

//            }

//            printf("\n");

//            for(int j = 1 ; j <= n ; j++){

//                if(1 << (j - 1) & i)

//                    printf("j = %d, dp[i][%d] = %d\n", j, j, dp[i][j]);

//            }

//        }

//        printf("dp\n");

//        int ans = inf;

//        for(int i = 1 ; i <= n ; i++)

//            ans = gmin(ans, dp[(1 << n) - 1][i]);

        if(n == 1)

            printf("0\n");

        else

            printf("%d\n", dp[(1 << n) - 1][1]);

    }

    return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值