现在的位置: 首页 > 综合 > 正文

HDU 3001 Travelling (状态压缩DP)

2019年03月01日 ⁄ 综合 ⁄ 共 2491字 ⁄ 字号 评论关闭

题意:有n个点(n<=10),m条边的图(可能有重边),问从任一点走遍整个图的最短费用和,其中每个点最多只能经过两次。

思路:tsp问题,状压dp。三进制压缩一个点已被访问的次数作为状态s,dp[s][i]代表状态s下最后一个到达的点位i的最小费用。递推方程:在dp[s][i]已计算出来的情况下,枚举下一个到达的点k,那么dp[news][k] = min{ dp[news][k]+Edge[i][k],dp[news][k] },初始化:dp为INF,dp[3的i次方][i]=0.

注意题目存在重边,因此只保留边长最小的。

两种写法,递推的效率高但顺序相对难掌握,记忆化搜索好些但效率下。

递推:

#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;

int n,m;
int tri[12],dig[59050][12];//dig[s][i]表示状态s的第i位
int dp[59050][12];
int Edge[11][11];
int ans;

void init()//初始化计算tri[i]为3的i次方和dig[s][i]
{
    tri[0]=1;
    for(int i=1;i<=10;++i) tri[i] = tri[i - 1] * 3;

    for(int s=0;s<59049;++s)
    for(int i=0,t = s;i<10;++i,t/=3) dig[s][i]=t%3;
}

void input()
{
    int a,b,c;
    memset(Edge,INF,sizeof(Edge));
    while(m--)
    {
        scanf("%d%d%d",&a,&b,&c);
        --a,--b;
        Edge[b][a]=Edge[a][b]=min(Edge[a][b],c);//存在重边,只记录边长最小的
    }
}

void getdp()
{
    memset(dp,INF,sizeof(dp));
    ans = INF;
    for(int i=0;i<n;++i) dp[ tri[i] ][i] = 0;//初始化dp边界:如果s中,仅有i被访问且只被访问一次,费用为0
    for(int s=0;s<tri[n];++s)
    {
        int vall = 1;//计算s是否每个点都访问了一遍
        for(int i=0;i<n;++i)
        {
            if(dig[s][i]==0) vall = 0;
            if(dp[s][i]==INF) continue;
            for(int k=0;k<n;++k)//枚举i所到的下一个点k
            {
                if(i == k) continue;
                if(Edge[i][k]==INF || dig[s][k]>=2) continue;//如果不存在边<i,k>或者k已被访问2次以上则不进行更新
                int news = s + tri[k];//news表示在s的基础上再访问k一次所得状态
                dp[news][k] = min(dp[news][k],dp[s][i]+Edge[i][k]);
            }
        }
        if(vall)
            for(int i=0;i<n;++i) ans = min(ans,dp[s][i]);//如果每个点都访问过,更新ans
    }
}

void getans()
{
    printf("%d\n",ans == INF? -1:ans);
}

int main()
{
    init();
    while(cin>>n>>m)
    {
        input();
        getdp();
        getans();
    }
    return 0;
}

记忆化搜索:

#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;

int n,m;
int tri[12],dig[59050][12];
int dp[59050][12];
int Edge[11][11];
int ans;

void init()
{
    tri[0]=1;
    for(int i=1;i<=10;++i) tri[i] = tri[i - 1] * 3;

    for(int s=0;s<59049;++s)
    for(int i=0,t = s;i<10;++i,t/=3) dig[s][i]=t%3;
}

void input()
{
    int a,b,c;
    memset(Edge,INF,sizeof(Edge));
    while(m--)
    {
        scanf("%d%d%d",&a,&b,&c);
        --a,--b;
        Edge[b][a]=Edge[a][b]=min(Edge[a][b],c);
    }
}

int dfs(int s,int i)
{
    if(dp[s][i]!=-1) return dp[s][i]; //体现记忆化
    int cur = INF;//初始化为INF,如果出现不合法状态会返回INF
    if(s==tri[i]) cur = 0;//如果s中,仅有i被访问且只被访问一次
    else
        if(dig[s][i])//如果i在s中确实被访问了
            for(int k=0;k<n;++k)//枚举到i的上一个点k
                    if(dig[s][k] && Edge[k][i]!=INF && k!=i)//异于i的点k必须在s中被访问过,且存在边<k,i>
                            cur = min(cur,dfs(s-tri[i],k)+Edge[k][i]);
    return dp[s][i] = cur;
}

void getdp()
{
    memset(dp,-1,sizeof(dp));
    ans = INF;

    for(int s=0;s<tri[n];++s)
        {
            int vall=1;//计算s是否每个点都访问了一遍
            for(int i=0;i<n;++i)
            {
                if(dig[s][i]==0) vall = 0;
                dfs(s,i);
            }
            if(vall) for(int i=0;i<n;++i) ans = min(ans,dp[s][i]);//如果每个点都访问过,更新ans
        }
}

void getans()
{
    printf("%d\n",ans == INF? -1:ans);
}

int main()
{
    init();
    while(cin>>n>>m)
    {
        input();
        getdp();
        getans();
    }
    return 0;
}

抱歉!评论已关闭.