洛谷P1171 售货员的难题【状压DP】

题目描述

某乡有n个村庄( 1 &lt; n ≤ 20 1&lt;n\le20 1<n20 ),有一个售货员,他要到各个村庄去售货,各村庄之间的路程 s ( 0 &lt; s &lt; 1000 ) s(0&lt;s&lt;1000) s(0<s<1000)是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。

输入格式:

村庄数n和各村之间的路程(均是整数)。

输出格式:

最短的路程。

说明

输入解释
3 {村庄数}
0 2 1 {村庄1到各村的路程}
1 0 2 {村庄2到各村的路程}
2 1 0 {村庄3到各村的路程}


题目分析

TSP问题,也算是状压DP经典吧

我们以一串二进制数表示村庄的集合(状态)
1表示该村庄访问过,0表示没有

d p [ i ] [ j ] dp[i][j] dp[i][j]表示从起点到第j号点,且到达时状态恰好为i的最短路

则最后答案就是 m i n ( d p [ ( 1 &lt; &lt; n ) − 1 ] [ i ] + m a p [ i ] [ 1 ] ) min(dp[(1&lt;&lt; n) -1][i] + map[i][1]) min(dp[(1<<n)1][i]+map[i][1]) ( 2 &lt; = i &lt; = n ) (2&lt;=i&lt;=n) (2<=i<=n)
其中map数组是题目给定的各村庄间距离

而求取dp数组的方法,我们可以借鉴Floyd的思想

具体代码:

for(int i=0;i<=(1<<n)-1;++i)
for(int j=1;j<=n;++j)
if( ( (1<<j-1)&i )==0 )
for(int k=1;k<=n;++k)
if( ( (1<<k-1)&i) )
dp[i|(1<<j-1)][j]=min(dp[i|(1<<j-1)][j],dp[i][k] + rem[k][j]);

如何解释呢
第一层循环 i 枚举每个状态

第二层循环 j 枚举下一步到达的点

if( !( (1 << j-1) & i) ) 这句判断 j 是否已访问
1左移j-1位,则此时只有第j位是1
若状态 i 的第 j 位为1,则&与运算返回1,表示已访问,否则没访问

第三层循环枚举中介点k
其中if语句判断同上

d p [ i ∣ ( 1 &lt; &lt; j − 1 ) ] [ j ] = m i n ( d p [ i ∣ ( 1 &lt; &lt; j − 1 ) ] [ j ] , d p [ i ] [ k ] + r e m [ k ] [ j ] ) ; dp[i|(1&lt;&lt;j-1)][j]=min(dp[i|(1&lt;&lt;j-1)][j],dp[i][k] + rem[k][j]); dp[i(1<<j1)][j]=min(dp[i(1<<j1)][j],dp[i][k]+rem[k][j]);
i ∣ ( 1 &lt; &lt; j − 1 ) i|(1&lt;&lt;j-1) i(1<<j1) 将状态 i i i的第 j j j为置为1得到下一步的状态
d p [ i ] [ k ] + m a p [ k ] [ j ] dp[i][k] + map[k][j] dp[i][k]+map[k][j]表示在当前状态i中寻找中介点检查最短路是否可以更新

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=1100010;
int n,ans=2e9;
int dp[maxn][25];
int rem[25][25];

int main()
{
    n=read();
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
    rem[i][j]=read();

    memset(dp,67,sizeof(dp));
    dp[1][1]=0;//状态1表示此时只有1号点访问过

    for(int i=0;i<=(1<<n)-1;++i)//dp过程解释如上
    for(int j=1;j<=n;++j)
    if( ( (1<<j-1)&i )==0 )
    for(int k=1;k<=n;++k)
    if( ( (1<<k-1)&i) )
    dp[i|(1<<j-1)][j]=min(dp[i|(1<<j-1)][j],dp[i][k] + rem[k][j]);

    for(int i=2;i<=n;i++)//最后从状态(1<<n)-1(二进制全为1)中寻找到1最短的点
    ans=min(ans,dp[(1<<n)-1][i] + rem[i][1]);

    printf("%lld",ans);
    return 0;
}

n<=20的数据范围在状压题中算是开到极限了,蒟蒻担心卡常所以开了O2,最大的点跑了541ms,目测不开O2也是能过的


9.15——Update

再提供一种状压搜索的思路
虽然试了好几次都卡不过最后一个点
所以仅供参考学习吧,因为这种思路也挺常用的

dp数组含义依然同上,
初始 d p [ 1 ] [ 1 ] = 0 dp[1][1]=0 dp[1][1]=0

搜索具体代码,应该不难理解

void dfs(int x,int u)//x是当前状态,u是当前到达的结点
{
    for(int i=1;i<=n;++i)
    if(((1<<i-1)&x)==0)//枚举还未到达的点
    if(dp[x|(1<<i-1)][i]>dp[x][u]+rem[u][i])//如果可以更新就继续搜索
    {
        dp[x|(1<<i-1)][i]=dp[x][u]+rem[u][i];
        dfs(x|(1<<i-1),i);
    }
}

最终答案判断方法与上述DP相同

虽然这种方法艹不过此题,但建议要理解这种思路

#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=1050000;
int n,ans=2e9;
int rem[22][22],dp[maxn][22];

void dfs(int x,int u)
{
    for(int i=1;i<=n;++i)
    if(((1<<i-1)&x)==0)
    if(dp[x|(1<<i-1)][i]>dp[x][u]+rem[u][i])
    {
        dp[x|(1<<i-1)][i]=dp[x][u]+rem[u][i];
        dfs(x|(1<<i-1),i);
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
    rem[i][j]=read();
    
    memset(dp,67,sizeof(dp)); dp[1][1]=0;
    dfs(1,1);
    
    for(int i=1;i<=n;++i) 
    ans=min(ans,dp[(1<<n)-1][i]+rem[i][1]);
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值