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
但一个明显的区别是这题每个点至少访问一次,至多访问两次,因此很显然就不能floyd处理邻接矩阵了,原因可以看poj3311的题解。这题还有一个坑点是起点未知。
因此这题的dp方式很奇葩,可能有部分人暂时无法接受。dp[s][i]表示状态为s,到达点i的最小代价。这里s应该视作3进制数。由于有多个初值,因此从点i出发的dp[tri[i]][i]=0,tri数组含义见代码;
这里有一个问题初始化多个起点没问题吗?因为事实只可能有一个起点啊。
深入想想是可以的,因为本题采用的是刷表的思想(小白上有提及),而且每次dp都是一个一个点加上去的,因此有很明显的分层现象,而本层是通过上一层比较大小得出的,虽然会有很多起点,但是经过比较,最终只会是最小的赋值给本层,有点类似打擂台的思想,可以由数学归纳法看出每个dp都只可能对应一个起点,而且不会发生都多个起点值融合成一个值,因为每个dp只可能由一个值转移过来,都是独立的,不会冲突。
这题的dp方法,真心觉得需要学习一下,这是以往的题目没出现过的。原来dp也可以这样啊,太神奇了!
代码:
#include<cstdio> #include<iostream> #include<cstring> #define Maxn 59049 using namespace std; const int inf=0x3f3f3f3f; int tri[]={0,1,3,9,27,81,243,729,2187,6561,19683,59049}; void table(){ //打表-3的倍数 int t=1; for(int i=1;i<=11;i++){ printf("%d,",t); t*=3; } } int bit[Maxn][11]; //bit[i][j]表示i的三进制表示第j位数 int adj[11][11],dp[Maxn][11]; void pre(){ for(int i=0;i<Maxn;i++){ int t=i; for(int j=1;j<=10;j++){ bit[i][j]=t%3; t/=3; } } } int main() { int n,m,a,b,c; //table(); pre(); //预处理bit数组 while(cin>>n>>m){ memset(adj,0x3f,sizeof adj); for(int i=0;i<m;i++){ cin>>a>>b>>c; adj[a][b]=adj[b][a]=min(adj[a][b],c); } int ans=inf; memset(dp,0x3f,sizeof dp); //初始化为非法状态 for(int i=1;i<=n;i++) dp[tri[i]][i]=0; //初始化多个起点,多路推进 for(int s=0;s<tri[n+1];s++){ //枚举每个状态 bool flag=true; //表示所有点已被遍历 for(int i=1;i<=n;i++){ //检查s的每一位 if(!bit[s][i]) flag=false; //点i没有被遍历 if(dp[s][i]==inf) continue; //非法,无法刷表 for(int j=1;j<=n;j++){ //枚举接下来需要遍历的点 if(bit[s][j]>=2||adj[i][j]==inf) continue; //已访问2次 int ns=s+tri[j]; dp[ns][j]=min(dp[ns][j],dp[s][i]+adj[i][j]); } } if(flag){ //是解 for(int i=1;i<=n;i++) //枚举终点 ans=min(ans,dp[s][i]); } } if(ans==inf) ans=-1; printf("%d\n",ans); } return 0; }