hdu3001Travelling(旅行商问题+路径压缩)

Problem Description

After coding so many days,Mr Acmer wants to have a good rest.So travelling is the best choice!He has decided to visit n cities(he insists on seeing all the cities!And he does not mind which city being his start station because superman can bring him to any city at first but only once.), and of course there are m roads here,following a fee as usual.But Mr Acmer gets bored so easily that he doesn't want to visit a city more than twice!And he is so mean that he wants to minimize the total fee!He is lazy you see.So he turns to you for help.
题意:给你一个图,问走完所有点的最短路径,每个点最多访问两次
思路:旅行商问题,一定是要压缩路径的,但是怎么压缩呢?
简单的旅行商问题是用一个集合s表示未访问的点,以及i为起点花费的最短路径,
这样我们就能将s从所有点都未访问((1<<n)-2),以j为起点,以k为转折点开始向下dp,直到s为0
然后选择一个路径最短的终点(也可以说是起点,但是点都访问完了,所有就叫终点了...)即可求得最优解
复杂度(2^n)*(n^2),在n>10的时候比暴力的O(n!)好太多,而且这类题都用二进制表示的集合,特别好构造
 
咳咳...说回咱们这道题,咱么这道题也是和传统的旅行商问题特别相似,但是限制了每个点只能选择最多两次,
不是无数次,所以我们的选择集合增加为了三种,还没走,走一次,走两次,n最多为10所以就应该是10^3种状态,
0表示所有点都还没访问,全部都为3表示所有点都访问了两次,状态转移方程也跟原始的差不多
                      dp[i][j]=min(dp[i][j],dp[l][k]+g[j][k])
i表示当前正在处理的状态,l(eo)表示i状态减去k这个转移点的状态
j表示访问完i状态种所有点后的起点,k表示用来转移的点,该转移方程表示
走过i状态下所有点及所有次数后来到j点,需要的最短路径可以由
i状态下还少访问j这个点一次的所有可能点+该可能点到j的距离
推出来,是不是很绕,哈哈哈.
具体怎么构造状态代码里面有,可以当作模板记住,
以后遇到这种多状态数而非传统0/1集合表示的题目说不定就A了呢
可以注意的是,当判断到某个点选择两次的状态时,其他点相同,
它选择一次的状态,也一定以及被推出来了,因为咱么是从小到大的推,
理解了真的觉得这种压装方程真的很奇妙,太棒了
AC代码:
#include<iostream>
#include<string.h>
#include<cmath>
#include<set>
#include<map>
#include<string>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
#include<algorithm>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
const int maxn = 13;
int n, m,ans;
int g[maxn][maxn];
int dic[maxn] = { 0,1,3,9,27,81,243,729,2187,6561,19683,59049 };
//相当于0/1集合动态规划的0 1 2 4 8
int tri[59050][11];
int dp[59050][11];
void init() {
    for (int i = 0; i <=59049; i++) {//10^3个最大状态数,
        int t = i;
        for (int j = 1; j <= 10; j++) {
            //把该状态下的不同点的访问次数先求出来,像之前0/1集合就是直接位运算了,谁管你这些哦
            //但这是三种状态,莫得办法呀
            tri[i][j] = t % 3;
            t /= 3;
        }
    }
}
void solve() {
    memset(dp, 0x3f3f, sizeof(dp));
    for (int i = 1; i <= n; i++) {
        dp[dic[i]][i] = 0;
    }//老样子,每个点最开始啥都没访问距离初始化0
    for (int i = 0; i < dic[n+1]; i++) {
        bool f = 1;
        for (int j = 1; j <= n; j++) {
            if (tri[i][j] == 0) {
                f = 0; continue;
    //j这个点不在这个状态中推啥推嘛,然后就是该状态存在没有走过的点,f标记0,这不是满足结果的状态(所有点都要访问至少一遍)
            }
            for (int k = 1; k <= n; k++) {
                if (tri[i][k] == 0)continue;
                //同上,该状态下这个点没访问过,不能用来转移
                int l = i - dic[j];
                if (j == k)continue;
                //j==k,自己给自己转移,有用么?
                if (g[k][j] == inf)continue;
                //路都么的,转移啥嘛
                dp[i][j] = min(dp[i][j], dp[l][k] + g[k][j]);
            }
        }
        if (f) {
            //这种状态下所有点都被访问过了(管他访问几次,反正满足条件了),就开始求最优解
            for (int j = 1; j <= n; j++)
                ans = min(ans, dp[i][j]);
        }
    }
}
int main() {
    //freopen("test.txt", "r", stdin);
    init();
    while (~scanf("%d%d", &n, &m)) {
        memset(g, 0x3f3f, sizeof(g));
        //for (int i = 1; i <= n; i++)g[i][i] = 0;//加不加无所谓,反正自己推自己那种可能本身就不可取,在上面的代码种被我优化掉了
        ans = inf;
        for (int i = 1; i <= m; i++) {
            int a, b,c;
            scanf("%d%d%d", &a, &b, &c);
            g[a][b] = g[b][a] = min(g[a][b], c);//....
        }
        solve();
        if (ans == inf) {
            printf("-1\n");
        }
        else {
            printf("%d\n", ans);
        }
    }
    return 0;
}

 

 
posted @ 2021-03-14 21:57  cono奇犽哒  阅读(98)  评论(0)    收藏  举报