做题感悟:这题真郁闷,之前做过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 ;
}