题目链接~~>
做题感悟:这题真郁闷,之前做过POJ上的一个题和这个类似,但是不知为啥一直不对,看了一下别人的另一种思想才A。
解题思路:Floyd + 状态压缩
因为要参观的城市才16个,可以用状态压缩枚举每一种状态。
状态转移方程:dp[ S |(1<<j) ][ j ] = max( dp[ S |(1<<j) ][ j ],dp[ S ][ i ] - d[ v ][ u ] - c[ j ] + e[ j ] ).即:已经知道 dp[ S ][ i ] 以 i 为中间点更新 j 点的 dp[ S |(1<<j) ][ j ] 。
注意:有重边,e[ i ] 和 c[ i ] 不能合并,必须要先买证才能得到工资,开始点有可能包含在要参观的点中 。
代码:
#include<stdio.h> #include<iomanip> #include<vector> #include<queue> #include<fstream> #include<string.h> #include<stdlib.h> #include<string.h> #include<algorithm> #include<iostream> #define INT long long int using namespace std ; const int INF = 999999999 ; const int MX = 100 + 10 ; const int MY = 20 ; int n,m,num,tot ; int dp[1<<16][MY],V[MY],e[MY],c[MY],d[MX][MX] ; void init(int n) { for(int i=0 ;i<n ;i++) for(int j=0 ;j<n ;j++) i==j ? d[i][j]=0 : d[i][j]=INF ; } void Floyd() // 最短路 { for(int k=0 ;k<n ;k++) for(int i=0 ;i<n ;i++) for(int j=0 ;j<n ;j++) d[i][j]=min(d[i][k]+d[k][j],d[i][j]) ; } void DP_SC(int pos) { if(tot-c[pos]>=0)// 到达 0 点工作所剩钱 dp[1<<pos][pos]=tot-c[pos]+e[pos] ; dp[0][pos]=tot ; // 到达 0 点没工作所剩钱 for(int S=0 ;S<(1<<num) ;S++) for(int i=0 ;i<num ;i++) if(dp[S][i]!=-1)// 以 i 为中间点去更新 j 点 { for(int j=0 ;j<num ;j++) if(i!=j&&!(S&(1<<j))) { int u = V[j],v=V[i] ; if(dp[S][i]>=d[v][u]+c[j]) dp[S|(1<<j)][j]=max(dp[S|(1<<j)][j],dp[S][i]-d[v][u]-c[j]+e[j]) ; } } int S=(1<<num)-1 ; bool flag=false ; for(int i=0 ;i<num ;i++) if(dp[S][i]>=d[V[i]][0]) { flag=true ; break ; } cout<<(flag ? "YES" : "NO")<<endl ; } int main() { int Tx,u,v,w ; scanf("%d",&Tx) ; while(Tx--) { scanf("%d%d%d",&n,&m,&tot) ; init(n) ; for(int i=0 ;i<m ;i++) { scanf("%d%d%d",&u,&v,&w) ; u-- ; v-- ; d[u][v]=d[v][u]=min(d[u][v],w) ; } scanf("%d",&num) ; int pos=-1 ; for(int i=0 ;i<num ;i++) { scanf("%d%d%d",&V[i],&e[i],&c[i]) ; V[i]-- ; if(!V[i]) pos=i ; } memset(dp,-1,sizeof(dp)) ; if(pos==-1) { V[num]=0 ; e[num]=0 ; c[num]=0 ; pos=num++ ; } Floyd() ; DP_SC(pos) ; } return 0 ; }
题目链接~~>
做题感悟:这题和上面那题差不多,只要理解意思知道思路就ok了。
解题思路:Floyd + 状态压缩
(1)输入必须拜访的城市时要记录一下,弄成二进制的形式(这里记为 P)便于后面查看是否完成,先Floyd 一下,求出任意两点之间的距离。
(2)再用状态压缩枚举每一种状态dp [ i ] [ j ] 代表在状态 i 下最终到达 j 节点时所花费的最少时间,最终答案取 i 与 P相与 如果等于 P 就记录 i 状态中所拜访的城市个数与最优解比较的最大值。
代码:
#include<stdio.h> #include<iomanip> #include<vector> #include<queue> #include<fstream> #include<string.h> #include<stdlib.h> #include<string.h> #include<algorithm> #include<iostream> #define INT long long int using namespace std ; const double INF = 99999999 ; const int MX = 200 + 10 ; const int MY = 15 ; int n,m,mt ; double tot ; double c[MX],d[MX][MX],dp[1<<16][17] ; void init() // 初始化 { for(int i=0 ;i<n ;i++) for(int j=0 ;j<n ;j++) i==j ? d[i][j]=0 : d[i][j]=INF ; for(int S=0 ;S<(1<<n) ;S++) for(int i=0 ;i<n ;i++) dp[S][i]=-1 ; } void Floyd() // 求任意两点的最短路径 { for(int k=0 ;k<n ;k++) for(int i=0 ;i<n ;i++) for(int j=0 ;j<n ;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]) ; } int judge(int S) // 记录 S 中 1 的个数 { int sum=0 ; for(int i=0 ;i<n ;i++) if(S&(1<<i)) sum++ ; return sum ; } void DP_SC() // 状态压缩枚举 { int ans=0 ; bool flag=false ; for(int i=0 ;i<n ;i++) if(d[0][i]!=INF) dp[1<<i][i]=d[0][i]+c[i] ; for(int S=0 ;S<(1<<n) ;S++) for(int i=0 ;i<n ;i++) if(dp[S][i]!=-1) { for(int j=0 ;j<n ;j++) if(i!=j&&!(S&(1<<j))) { if(dp[S|(1<<j)][j]==-1) dp[S|(1<<j)][j]=dp[S][i]+d[i][j]+c[j] ; else dp[S|(1<<j)][j]=min(dp[S|(1<<j)][j],dp[S][i]+d[i][j]+c[j]) ; } if(((S&mt)==mt)&&dp[S][i]+d[i][0]<=tot) { flag=true ; ans = max(ans,judge(S)) ; } } if(flag) cout<<ans<<endl ; else cout<<"No Solution"<<endl ; } int main() { double len ; int u,v,x,kind ; while(scanf("%d%d%lf",&n,&m,&tot),(n+m+tot)) { init() ; tot=tot*12.0 ; mt=0 ; for(int i=0 ;i<m ;i++) { scanf("%d",&x) ; x-- ; mt=mt|(1<<x) ; // 以二进制的形式记录必须拜访的城市 } for(int i=0 ;i<n ;i++) scanf("%lf",&c[i]) ; while(scanf("%d%d%lf%d",&u,&v,&len,&kind)&&(u+v+len+kind)) { u-- ; v-- ; double ct = kind ? len/120.0 : len/80.0 ; d[u][v]=d[v][u]=min(d[u][v],ct) ; // 可能重边 } Floyd() ; DP_SC() ; } return 0 ; }