【状压dp】poj 2686 Traveling by Stagecoach

状压dp:针对集合的dp

虽然可以把城市看作顶点,道路看作边建图,但是由于有车票相关的限制,无法直接使用Dijkstra算法求解。不过,这种情况下只需要把状态作为顶点,而把状态的转移看成边来建图就可以很好地避免这个问题。
让我们考虑一下“现在在城市v,此时还剩下的车票的集合为S”这样的状态。从这个状态出发,使用一张车票i属于S,移动到相邻的城市u,就相当于转移到了“在城市u,此时还剩下的车票的集合为S\{i}”这个状态。把这个转移看成一条边,那么边上的花费是(v-u间道路的长度)/t。按照上述方法所构的图,就可以用普通的Dijkstra算法求解了。
集合S使用状态压缩的方法表示就可以了。由于剩余的车票的集合S随着移动元素个数不断变小,因此这个图实际上是个DAG(请参照2.5节)。计算DAG的最短路不需要使用Dijkstra算法,可以简单地通过DP求解。在这道题中如下图所示。

思路:做这道题就是为了学习状压dp,题目特征是问题规模很小,涉及到选择几张车票,以及每张车票对应哪条路

dp[S][v]还剩下的车票集合为S,现在在V的最短时间

时间复杂度 n*m^2*2^n

参考博客

#include<iostream>
#include<cstring>>
#include<algorithm>
#include<cstdio>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,a,b,p;
const int N=10;
const int M=40;
int t[N];
double dp[1<<N][M];
int mp[M][M];

void solve()
{
    //memset(dp,INF,sizeof(dp));
    for(int i=0;i<(1<<n);++i)          //memset会wa
        fill(dp[i]+1,dp[i]+m+1,INF);
    dp[(1<<n)-1][a]=0;
    for(int i=(1<<n)-1;i>=0;--i)
    {
        for(int u=1;u<=m;++u)
        {
            for(int j=0;j<n;++j)        //枚举每个转移方向
            {
                if(i&(1<<j))            //如果剩下的票的集合i中还有j
                {
                    for(int v=1;v<=m;++v)
                    {
                        if(mp[u][v]>=0)
                            dp[i&~(1<<j)][v]=min(dp[i&~(1<<j)][v],dp[i][u]+(double)mp[u][v]/t[j]);
                    }
                }
            }
        }
    }
    double res=INF;
    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);       //lf 会wa
}

int main()
{
    while(cin>>n>>m>>p>>a>>b)
    {
        if(!n&&!m&&!p&&!a&&!b) break;
        for(int i=0;i<n;++i)
            cin>>t[i];
        memset(mp,-1,sizeof(mp));
        for(int i=1;i<=p;++i)
        {
            int x,y,d;
            cin>>x>>y>>d;
            mp[x][y]=mp[y][x]=d;
        }
        solve();

    }
    return 0;
}