这几天碰到了状压DP的题,然后学一学,从白书开始学,持续更新中:
//状压DP学习:
旅行商问题:
给定n个顶点组成的带权有向图的距离矩阵d(I,j)(INF代表没有边),
要求从顶点0出发,经过每个顶点恰好一次后再回到顶点0,
问所经过的边的总权重的最小值是多少?
限制条件:
2<=n<=15
0<=d(i,j)<=1000
样例见白书P191
假设现在已近访问过的顶点的集合(起点0当做还未访问过的顶点)为S
当前所在的顶点为v
dp[S][v]表示从v出发访问剩余的所有顶点,最终回到顶点0的路径的权重总和的最小值。
由于从v出发移动到任意一个节点 u不属于S :
获得状态转移方程:
dp[v][0]=0;
dp[S][v]=min(dp[S|u][u]+d(v,u) /*u不属于S*/);
//下面的这个dp可以在 O(2^n * n^2) 的时间内完成计算,
int n;
int d[MAXN];
int dp[1<<MAXN][MAXN];//用二进制表示某一个位置的状态
int rec(int S,int v)//已经选取的集合S和当前所处的位置v
{
if(dp[S][v]>=0)//如果该位置存在元素,直接返回
return dp[S][v];
if(S==(1<<n)-1/*所有的元素都存在S集合中*/&&v==0)// && 又回到了原点
return dp[S][v]=0;//初始化距离为0
int res=INF;//初始化
for(int u=0;u<n;++u)//遍历所有的点
{
if((S>>u&1)==0)//第u位没有选择
res=min(res,rec(S|1<<u,u)/*将u位置加入集合*/+d[v][u]);//前面的集合加上d[v][u]
}
return dp[S][v]=res;
}
void solve()
{
clean(dp,-1);//初始化dp
cout<<rec(0,0)<<endl;//从0,0开始
}
int main()
{
std::ios::sync_with_stdio(false);
solve();
}
//或者用递推的方式写出dp式子:
int dp[1<<MAXN][MAXN];
void solve()
{
for(int S=0;s<1<<n;++S)
fill(dp[S],dp[S]+n,INF);
dp[(1<<n)-1][0]=0;
for(int S=(1<<n)-2;S>=0;S--)
{
for(int v=0;v<n;++v)
{
for(int u=0;u<n;++u)
{
if((S>>u&1)==0)
dp[S][v]=min(dp[S][v],dp[S|1<<u][u]+d[v][u]);
}
}
}
cout<<dp[0][0]<<endl;
}
然后是POJ-2686
//POJ-2686
旅行家出行,共有m个城市,城市之间有道路相连,
从一个城市到另一个城市去需要马车才行,乘坐马车需要使用车票
每一张车票只能使用一次且只能通过一条道路,每张车票能使用不同数量的马
通过道路所用的时间是:道路长度/马匹的数量
旅行家共有n张车票,第i张车票上记录马的数量是ti,
求出从a城市到b城市所需要的最短时间,如果无法到达则输出impossinle
int n,m,a,b;
int t[MAXN];
int d[MAXN][MAXN];
double dp[1<<MAXN][MAXN];
void solve()
{
for(int i=0;i<1<<MAXN;++i)
clean(dp[i],dp[i]+m,INF);
dp[(1<<n)-1][a-1]=0;
double res=INF;
for(int S=(1<<n)-1;S>=0;--S)
{
res=min(res,dp[S][b-1]);
for(int v=0;v<m;++v)
{
for(int i=0;i<n;++i)
{
if(S>>i&1)
{
for(int u=0;u<m;++u)
{
if(d[v][u]>=0)//存在
dp[S&~(1<<i)][u]=min(dp[S&~(1<<i)][u],dp[S][v]+(double)d[v][u]/t[i]);
}
}
}
}
}
if(res=INF)
cout<<"NO"<<endl;
else
cout<<res<<endl;
}