POJ 2686 Travelling by Stagecoach(状态压缩dp)

题目链接:http://poj.org/problem?id=2686

题意:百度翻译

思路:来自《挑战程序设计》 Page194

    虽然可以吧城市看作顶点,道路看作边建图,但是由于车票相关的限制,无法直接使用 Dijkstra 算法求解,不过,这种情况下只需要吧状态作为定点,而把状态转移看成边来构建图就可以很好的避免这个问题。

    让我们考虑一下 ”现在的城市v,此时还剩下的车票的集合为S“ 这样的状态,从这个状态出发,使用一张车票 i 属于集合 S 移动到相邻的城市u,就相当于转移到了 ”在城市 u ,此时剩下的车票的集合为 S/{i} “ 这个状态。把这个转移看成一条边,你么边上的花费就是(v-u间道路的长度)/ t ,按照上述的方法构图,就可以使用普通的 Dijkstra 来求解了

    集合S使用状态压缩的方法表示就可以了。由于剩余的车票的集合 S 随着移动元素个数的不断变小,因此整个图实际上就是个DAG(有向无环图),这样我们可以通过简单的 dp 来求解了,

个人感受:书上说的很有道理,但我觉得这算法很不讲理,这道NP-hard 题可以说是我目前接触的最难理解的题目之一了,我也花了好长的时间去百度学习,才有所领悟,但是代码还是写不出来。。惭愧啊;


代码中用到了一种 集合的整数表示 的巧妙方法,主要思想是二进制和位运算,详细见我另一篇blog 集合的整数表示


#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1 << 10;
const int maxm = 31;
const int INF = 1 << 29;

int n, m, p, a, b;
int t[maxm];
int d[maxm][maxm];   //图的邻接矩阵表示(-1表示没有边)

//dp[S][v] := 到达剩下的车票集合为S并且现在在城市v的状态所需要的最小花费
double dp[maxn][maxm];

void solve()
{
    for (int i = 0; i < (1 << n); i++)
        fill(dp[i], dp[i] + m + 1, INF);    //用足够大的值初始化

    dp[(1 << n) - 1][a] = 0;
    double res = INF;
    for (int i = (1 << n) - 1; i >= 0; i--){ // i表示当前集合 S 
        for (int u = 1; u <= m; u++){  //相邻城市u 
            for (int j = 0; j < n; j++){ 
                if (i & (1 << j)){ //如果集合 i 中存在 j; 
                    for (int v = 1; v <= m; v++){
                        if (d[v][u]){
                            //使用车票j,从v移动到u, 
                            dp[i & ~(1 << j)][v] = min(dp[i & ~(1 << j)][v], dp[i][u] + (double)d[u][v] / t[j]);
                        }
                    }
                }
            }
        }
    }
    for (int i = 0; i < (1 << n); i++)
        res = min(res, dp[i][b]);
    if (res == INF)
        printf("Impossible\n");
    else
        printf("%.3f\n", res);

}

int main()
{
    while(scanf("%d%d%d%d%d", &n, &m, &p, &a, &b)!= EOF){
        if(!n && !m)
        break;
        memset(d, 0, sizeof(d));
        for (int i = 0; i < n; i++)
            scanf("%d", &t[i]);
        for (int i = 0; i < p; i++){
            int u, v, c;
            scanf("%d%d%d", &u, &v, &c);
            d[u][v] = d[v][u] = c;
        }
        solve();
    }
    return 0;
}

真难。。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值