HDU 5418 Victor and World (可重复走的TSP问题,状压dp)
题意:
每个点都可以走多次的TSP问题:有n个点(n<=16),从点1出发,经过其他所有点至少1次,并回到原点1,使得路程最短。
思路:
给了很多重边,选最小的留下即可。任意点可能无法直接到达,所以先执行一次floyd,算出任意点对之间可达的最短距离。
(1)先考虑穷举的方法,将2~n个这n-1个数字的所有组合情况都算一遍,复杂度是 15!=1 3076 7436 8000,那是真的TSP了,不可能实现。
(2)上面的方法中有没有多余的计算量?有的!里面还是有贪心可以运用的地方。对于当前遍历过了哪些点,我们只需要知道最后一个点是什么,中间的点的顺序是所所谓的,那么最后一个遍历的可以是2~n,而中间那些可以是其他的2~n中的数。起点1的距离更新为0,接下来递推就行了。递推方法是,穷举所有的中间状态s,然后以这些状态去穷举下一个到达的点(此点不在s中)。
状态方程是: dp[s|(1<<(i-1))][i]=min(dp[s][j]+g[j][i] ); s表示已经遍历过的点,j是最后那个点,i是未遍历过的点,从j走到i。
290ms算可以了。
1 #include <bits/stdc++.h> 2 #define pii pair<int,int> 3 #define INF 0x3f3f3f3f 4 #define LL long long 5 using namespace std; 6 const int N=17; 7 int g[N][N], dp[1<<N][N]; 8 9 void floyd(int n) 10 { 11 for(int k=1; k<=n; k++) 12 for(int i=1; i<=n; i++) 13 for(int j=1; j<=n; j++) 14 g[i][j]=min( g[i][j], g[i][k]+g[k][j]); 15 16 } 17 18 int cal( int n ) 19 { 20 floyd(n); 21 memset(dp, 0x7f, sizeof(dp)); 22 dp[1][1]=0; 23 for(int s=1; s<(1<<n); s+=2) //穷举状态 24 { 25 for(int i=2; i<=n; i++) //设最后访问的点是i。i!=0 26 { 27 if( ( 1<<(i-1) ) & s ) continue; //s中的第i位必须为0,即未访问过。 28 for(int j=1; j<=n; j++) 29 { 30 if( s&(1<<(j-1)) ) //穷举s中出现过的1的位置。 31 dp[s|(1<<(i-1))][i]=min( dp[s|(1<<(i-1))][i], dp[s][j]+g[j][i] ); 32 } 33 } 34 } 35 int ans=INF; 36 for(int i=2; i<=n; i++) //最后访问的点不会是起点1。 37 ans=min(ans, dp[(1<<n)-1][i]+g[i][1]); 38 return ans==INF? 0: ans; //只有1个点的情况 39 } 40 int main() 41 { 42 //freopen("input.txt", "r", stdin); 43 int t, n, m, a, b, c; 44 cin>>t; 45 while(t--) 46 { 47 scanf("%d%d", &n, &m); 48 memset(g, 0x3f, sizeof(g)); 49 for(int i=0; i<=n; i++) g[i][i]=0; //初始化 50 for(int i=0; i<m; i++) 51 { 52 scanf("%d%d%d",&a,&b,&c); 53 g[b][a]=g[a][b]=min(g[a][b], c); 54 } 55 printf("%d\n", cal(n)); 56 } 57 return 0; 58 }