状压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; }