最近才看书,看到状态压缩。对于状态压缩DP,其实就是集合上的DP。
这需要我们了解一些位运算:
集合{0,1,2,3,....,n-1}的子集可以用下面的方法编码成整数
像这样,一些集合运算就可以用如下的方法来操作:
1.空集....................0
2.只含有第i个元素的集合{i}................1 << i
3.含有全部n个元素的集合{0,1,2,3,....,n - 1}.............(1 << n) - 1
4.判断第i个元素是否属于集合S.................................if(S >> i & 1)
5.向集合中加入第i个元素S ∪ {i}...............................S | 1 << i
6.从集合中除去第i个元素S \ {i}..................................S & ~(1 << i)
7.集合S和T的并集S∪T...............................................S | T
8.集合S和T的交集S∩T................................................S & T
而题目的意思是:一个人在m个城市的国家旅行,他有n张车票,这个国家有p条路,一条路连接两个城市,他要从a城市到b城市,从一个城市到另一个城市所需要的时间是路的长度除以车票的面值,面值是多少表示可以有多少匹马来拉,求最短的时间。
这题,不能直接用最短路径的算法来求解,有车票的限制。我们将在第i个城市看作一个状态,剩下的车票为一个集合S,从这个城市出发,使用一张车票到达城市j,现在他在第j个城市,状态为S \ {i},花费的时间就是路的长度除以车票的面值。
下面的是 AC的代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const double INF = 10000000000.00;
int n, m, p, a, b;
int ticket[10];
int map[32][32];
double dp[1 << 10][32];
double min(double a, double b) //取最小值
{
return a > b ? b : a;
}
void solve()
{
int i, j;
for(i = 0; i < 1 << n; i++) //初始化dp数组
{
for(j = 0; j < 32; j++)
dp[i][j] = INF;
}
dp[(1 << n) - 1][a - 1] = 0;
double res = INF;
for(int S = (1 << n) - 1; S >= 0; S--) //集合S
{
res = min(res, dp[S][b - 1]);
for(int v = 0; v < m; v++)
{
for(i = 0; i < n; i++) //枚举车票
{
if(S >> i & 1) //判断第i个车票是否属于集合S
{
for(int u = 0; u < m; u++)
{
if(map[v][u] >= 0) //城市v到u有路,使用第i个车票从v到u
{
dp[S & ~(1 << i)][u] = min(dp[S & ~(1 << i)][u], dp[S][v] + double(map[v][u]) / ticket[i]);
}
}
}
}
}
}
if(res == INF)
printf("Impossible\n");
else
printf("%.3lf\n", res);
}
int main()
{
// freopen("data.txt", "r", stdin);
int i, x, y, z;
while(cin >> n >> m >> p >> a >> b)
{
memset(map, -1, sizeof(map));
if(n == 0 && m == 0 && p == 0 && a == 0 && b == 0)
break;
for(i = 0; i < n; i++) //输入数据
cin >> ticket[i];
for(i = 0; i < p; i++)
{
cin >> x >> y >> z;
map[x - 1][y - 1] = z;
map[y - 1][x - 1] = z;
}
solve();
}
return 0;
}