2 1 1 2 100 3 2 1 2 40 2 3 50 3 3 1 2 3 1 3 4 2 3 10
100 90 7
这题的状态压缩不是二进制了,换到了三进制!!
这是为何??
题目中明确的说了每个点最多走2次,也就是说压缩为二进制并不能直接求出结果了,因为二进制只能代表一个点是否被走过的状态,而具体走过了几次却并不能记录!!然而我们题目要求可以走两次呀!怎么办?!大牛们想到了办法,压缩为三进制!
将状态压缩为三进制之后,那么显然,我们的状态数增多了,那么这些增加的状态数代表着什么呢?
举个栗子:将46化为3进制之后是1201,那么我们就可以暴力的来表示第1个点去过1次,第2个点没去过,第3个点去过2次,第4个点也去过1次!
用上面这个例子来说明一个问题,就是我们用三进制来压缩了题目要求的所有的状态,因为每个数位可以是2了,这个2是有意义的!就是表示某个点是否去过2次!
然后dp部分是状态压缩的常规解法,主要在于理解为何要化为3进制的状态压缩。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include<string.h> #include<algorithm> #include<math.h> #include<queue> using namespace std; typedef long long ll; int dp[60000][11];///dp[i][j]表示i状态下以j结尾的最短步数 int three[11];///three[i]表示3的i次方是多少 int digit[60000][11];///digit[i][j]表示状态i的第j位是什么数字(0,1,2) int tu[15][15]; int n,m; const int INF=99999999; void init() { three[0]=1; for(int i=1; i<11; i++) three[i]=three[i-1]*3; for(int i=0; i<three[10]; i++) { int tem=i; for(int j=0; j<10; j++) { digit[i][j]=tem%3; tem/=3; } } } int main() { init(); while(~scanf("%d%d",&n,&m)) { for(int i=0; i<n; i++) for(int j=0; j<n; j++) tu[i][j]=INF; for(int i=0; i<three[n]; i++) for(int j=0; j<n; j++) dp[i][j]=INF; while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); tu[a-1][b-1]=tu[b-1][a-1]=min(c,tu[a-1][b-1]); } for(int i=0; i<n; i++) dp[three[i]][i]=0;///dp的初始化,将起点都找出来 int ans=INF; for(int j=0; j<three[n]; j++) { bool flag=1; for(int i=0; i<n; i++) { if(digit[j][i]==0)flag=0;///只要三进制数中存在一个0,那么就说明还有点没有遍历完,就不能当做最终答案来求 if(dp[j][i]!=INF) for(int k=0; k<n; k++) if(tu[i][k]!=INF&&digit[j][k]!=2)///注意这个digit[j][k]!=2,因为如果j状态在k点已经走过两次了显然是不能继续往下走的 dp[j+three[k]][k]=min(dp[j][i]+tu[i][k],dp[j+three[k]][k]); } if(flag) for(int i=0; i<n; i++)///由于是3进制,不能方便的判断一串三进制数里面是否存在0,所以在dp过程中一边dp一边直接统计结果,也是迫于无奈T_T ans=min(ans,dp[j][i]); } if(ans>=INF) printf("-1\n"); else cout<<ans<<endl; } return 0; }