状压DP----TSP问题

HDU3001

要求解本题,首先我们需要了解tsp旅行商问题。

TSP问题:

旅行商问题,即TSP问题*(Traveling Salesman Problem)*又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

简而言之:求哈密顿回路。balabala

Problem Description: 给定一个节点数为n,边数为m的图,节点编号为0~n-1; 每条边的距离已知,求访问完所有点一次并且回到1号点的最短访问距离。

当n较小时,我们当然可以直接暴力深搜。但是当n较大时呢?我们可以压缩状态,成为状态间的转移。

如果需要从一个状态stat1更新到另一个状态stat2。那么stat2必然比stat1多一个1的位置j作为从stat1转换到stat2的跳板,而我们还需要知道stat1状态下当前的位置u,从stat1的u号节点到j号节点就得到了stat2。

状态表示: dp[stat][u]表示当前状态为stat,当前位置为u。

由上述分析很容易知道状态转移方程:

dp[stat1][u]=min(dp[stat1][u],dp[stat2][j]+cost[j][u]) 
//j--u连通,stat2>>j&1 = 1 &&stat2>>u = 0; stat1=stat2&1<<u; stat2=stat1^(1<<u); 
Solve-Code
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 20;
#define INF 0x3f3f3f3f
int f[N][N];  //f[i][j]记录是否联通,维护i,j的价值
int cost[N][N]; //好像没啥用
int dp[1 << N][N];
int main() {
	int n,m;
	cin >> n>>m;
	memset(f, INF, sizeof f);
	for (int i = 0, x, y,c; i < m; i++) {
		cin >> x >> y >> c;
		f[x][y] = f[y][x] = c;
	}
	memset(dp, INF, sizeof dp);
	dp[1][0] = 0;   //起点是0; start_stat=1
	for (int i = 1; i < (1 << n); i++) {//stat1
		for (int j = 0; j < n; j++) {//枚举起点 dp[3][1]=min(dp[3][1],dp[1][0]+f[0][1]);
			if ((i >> j & 1) == 0) {//存在0,即i状态下依旧可以转移。
				for (int u = 0; u < n; u++) {
					if ((i >> u & 1) == 1 && f[u][j] != INF && u != j) //i状态下u号可以作为起点,且u,j联通
						dp[i | 1 << j][j] = min(dp[i | 1 << j][j], dp[i][u] + f[u][j]);
				}
			}
		}
	}
	int ans = INF;
	for (int i = 0; i < n; i++) {//枚举返回的位置
		if(f[i][0]!=INF)
		ans = min(ans,dp[(1 << n) - 1][i]+f[i][0]);
	}
    if(ans==INF) 
        puts("-1");//未找到哈密顿回路。
    else
	cout << ans << endl;
	return 0;
}

hdu3001 TSP+三进制

Problem Description: 编码了这么多天,Acmer先生想好好休息一下。所以旅行是最好的选择!他决定去n个城市(他坚持要看到所有的城市!而且他不介意哪个城市是他的起点站,因为超人一开始可以带他去任何城市,但只能带一次。),当然这里有m条路,照常收费。但是阿克默先生很容易感到无聊,以至于他不想去一个城市超过两次!他太刻薄了,他想把总费用降到最低!你看他很懒。所以他向你求助。

最近刷到的一道很有意思的题目,在以前的印象中,状压一般是用二进制来压缩状态为一个数,但是本题中,一座城市可以经过两次,在一般的TSP的问题中,我们采取0表示未被访问,1表示被访问过。而本题应该如何解决捏?

三进制

TSP是在0,1表示是否访问,同样也代表着访问次数,那么0,1,2不是就恰巧满足了我们对本题的需求了啊。

我们仍然可以采用TSP的方法去考虑状态(这样可以方便理解) 。

dp[stat1][u]=min(dp[stat1][u],dp[stat2][j]);其中j号节点是stat2状态下被访问的节点。而u号节点是stat1状态下访问次数小于2(0或者1次)的节点。

那么如何区别三进制呢?

同二进制,我们需要不断除3,摸上3,当然在次过程中为了避免重复计算,我们开辟一个数组path_stat可以预处理所有状态下三进制各位的数值。

for(int i=0;i<59050;i++){
    int t=i;
    for(int j=1;j<=10;j++){
        path_stat[i][j]=t%3;
        t/=3;
    }
}

这样,我们就存储下所有状态下各位的数值。

结果考虑

接下来,我们考虑之前提到的状态转移

dp[stat1][u]=min(dp[stat1][u],dp[stat2][j]);其中j号节点是stat2状态下被访问的节点。而u号节点是stat1状态下访问次数小于2(0或者1次)的节点。

对此分析我们忽略了一点,对结果的判断。

我们应当如何去枚举合法结果以获得这个最小值呢?

首先保证所有节点都被访问到,1111111111-2222222222, 听起来是不是感觉有点大,其实咱们可以再次枚举所有状态,有无必要呢?我们可以边更新状态边更新答案。枚举状态时只需要加入一个判断是否可以作为答案的bool值。

再考虑当前回到起点前的节点,分析至此,应该可以发现起点问题了,如果我们去枚举起点,
考虑时间复杂度
T近似等于59050(状态层)*10(枚举的stat1第几位)10(转移的stat2状态)10(枚举10个起点)(内部指令条数,访问次数是可以不只一次)约为108,应该会TE(O((n3)(3^n)
怎么办捏????

太蠢了,又是没看完题目就跑路的一天,题目并没有要求回到起点😢😢。

所以我们上述方式依旧可以采取,大致O(n23n)左右,可以恰巧过掉。

Solve-Code
//hdu3001
#include <iostream>
#include <cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
#define INF  0x3f3f3f3f //据说是整数范围内最大值
int dp[60000][15];///dp[i][j]表示i状态下以j结尾的最短步数
int path_stat[60000][15];///digit[i][j]表示状态i的第j位是什么数字(0,1,2)
int tr[15];///tr[i]表示3的i次方是多少,便于后面转移
int graph[15][15];
int n, m;
void init() //位数统计起来,写了多少次,从来不知道init写对没......
{
    tr[1] = 1; //从第一位开始   1-0次 2-1次  11-10次
    for (int i = 2; i < 12; i++)
        tr[i] = tr[i - 1] * 3;
    for (int i = 0; i < tr[11]; i++) 
    {
        int t = i;
        for (int j = 1; j <= 10; j++)
        {
            path_stat[i][j] = t % 3;
            t /= 3;
        }
    }
}
int main()
{
    init();
    while (~scanf("%d%d", &n, &m))
    {
        memset(graph, INF, sizeof graph);
        memset(dp, INF, sizeof dp);
        for (int i = 0, a, b, c; i < m; i++) {
            scanf("%d%d%d", &a, &b, &c);
            graph[a][b] = graph[b][a] = c;
        }
        for (int i = 1; i <= n; i++)
            dp[tr[i]][i] = 0;       //从任意点出发,
        int ans = INF;
        for (int j = 0; j < tr[n+1]; j++) //状态
        {
            bool flag = true;              //检测该状态下是否将所有城市跑完
            for (int i = 1; i <= n; i++)
            {
                if (path_stat[j][i] == 0)flag = false; ///存在0,说明还有点没有遍历完,不能当作结果
                if (dp[j][i] == INF) continue;   //j状态下,i位置仍然为0,表示j状态下不可能在i位置
                for (int k = 1; k <= n; k++)
                    if (graph[i][k] != INF && path_stat[j][k] != 2)///tr[j][k]!=2,表示还可以到达。
                        dp[j + tr[k]][k] = min(dp[j][i] + graph[i][k], dp[j + tr[k]][k]);//同TSP,上文已经分析。二进制可以移位或,本苟若只知道三进制加......
            }
                if (flag)  //可作为答案
                    for (int i = 1; i <= n; i++) //当前位于i的不同,导致路线是不同的,所以需要枚举当前位置。
                        ans = min(ans, dp[j][i]);
            }
            if (ans == INF)
                puts("-1"); //puts自动换行
            else
                printf("%d\n", ans);
        }
        return 0;
    }
练习题目:

2021——蓝桥杯E题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值