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。
证明正确性:假设当前要更新的状态是S,u到v需要经过temp点。
1)temp在S中。那么没有重复走。
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;
}