【状压DP】哈密顿回路问题

DP 专栏收录该内容
33 篇文章 0 订阅

【状压DP】哈密顿回路问题

lzq同学在我准备午睡的时候发了一道蓝桥杯的题目给我,是哈密顿回路的。第一次看的时候是想DFS+双向搜索优化减小搜索树规模,然后写烂了(如果有大佬用搜索优化写出来了麻烦教教我这蒟蒻)。
后来请教了ph大佬,说是状压dp。确实,以前蓝书上见到过一道最短哈密顿路径的状压DP,所以学了点相关知识

哈密顿图定义

哈密顿图
通过图中所有顶点一次且仅一次的通路称为哈密顿通路。
通过图中所有顶点一次且仅一次的回路称为哈密顿回路。
具有哈密顿回路的图称为哈密顿图。
具有哈密顿通路而不具有哈密顿回路的图称为半哈密顿图。

最短Hamilton路径

思路

暴力搞的话复杂度为 O ( n ∗ n ! ) O(n*n!) O(nn!显然的NP难题,直接暴力很难搞
状态表示
f(i,j)
集合:所有从0走到j,走过的所有点是i的所有路径
属性:Min
状态计算
枚举倒数第二个点是哪个点,用倒数第二个点来分类
在这里插入图片描述
在这里插入图片描述
f ( i , j ) = m i n ( f ( i − { k } , k ) + a ( k , j ) ) f(i,j)=min(f(i-\{k\},k)+a(k,j)) f(i,j)=min(f(i{k},k)+a(k,j))
i的意义是一个集合,将这个集合转化为一个数,然后用位移运算来实现操作。
目标 f ( S ( n ) , n − 1 ) , S ( n ) = ( 1 < < n ) − 1 表 示 全 部 达 到 f(S(n),n-1),S(n)=(1<<n)-1表示全部达到 f(S(n),n1)S(n)=(1<<n)1

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=20,M=1<<N;
LL g[N][N],f[M][N];
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            cin>>g[i][j];
            
    memset(f,0x3f,sizeof f);
    
    f[1][0]=0;
    for(int i=0;i<(1<<n);i++)//集合状态
        for(int j=0;j<n;j++)
            if(i>>j&1)//看j点有没有到
                for(int k=0;k<n;k++)//枚举倒数第二个点
                    if(((i-(1<<j))>>k)&1)//i集合出去j点后k点也要存在
                        f[i][j]=min(f[i][j],f[i-(1<<j)][k]+g[k][j]);//状态计算最小值
    cout<<f[(1<<n)-1][n-1]<<endl;
    
    return 0;
}

蓝桥杯 E:回路计数

在这里插入图片描述

代码

状态表示
f ( i , j ) f(i,j) f(i,j)
集合:所有从1走到j,走过的所有点是i的所有路径
属性:方案数
状态转移: f ( i , j ) = ∑ k = 0 n − 1 f ( i − ( 1 < < j ) , k ) f(i,j)=\sum_{k=0}^{n-1}f(i-(1<<j),k) f(i,j)=k=0n1f(i(1<<j),k)
目标: ∑ j = 0 19 ( f ( ( 1 < < n ) − 1 , j ) ) \sum_{j=0}^{19}(f((1<<n)-1,j)) j=019(f((1<<n)1,j))
初始化:20个起点为1
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=20,M=1<<N;
LL st[N][N];
LL f[M][N];
int n;
int main()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            if(__gcd(i+2,j+2)==1)st[i][j]=1;
            
    for(int i=0;i<N;i++)f[1<<i][i]=1;//点集里只有自己,此时作为起点
    
    for(int i=0;i<(1<<N);i++)//集合状态
        for(int j=0;j<N;j++)
            if(i>>j&1)//看j点有没有到
                for(int k=0;k<N;k++)//枚举倒数第二个点
                    if(((i-(1<<j))>>k)&1)//i集合除去j点后k点也要存在
                    	if(st[k][j])f[i][j]+=f[i-(1<<j)][k];//状态计算
    LL ans=0;
    for(int i=0;i<N;i++)ans+=f[(1<<N)-1][i];//(1<<N)-1一定要加括号!
    cout<<ans<<endl;
    
    return 0;
}

CodeForces 11D A Simple Task (待补)

https://codeforces.com/problemset/problem/11/D
在这里插入图片描述

思路

题目越短,难度越大hh
这道题就是求长度>=3的哈密顿路径数。
哈密顿回路状压DP计数,这题应该是蓝桥杯的原题了吧hh,远古题目对标当时CF2200分。哇,我怎么敢的呀。
参考博客中学生吊锤大学生系列

由于去重比较恶心,所以这题暂时放一下,但是那个博客真滴很牛逼,强烈推荐看一看

代码

在这里插入代码片

小结

状态方程表示含义
f(i,j)从起点,路径经过节点状态集合为i,到目标点j
f(1<<i,i)从0开始,路径上只有起点i
f(1<<(i-1),i)从1开始,路径上只有起点i
f(i-(1<<j),k)从0开始到k点,路径还没经过j
f(1<<S-1,j)S表示总节点数,从起点0走到终点j,走完所有节点
if(i>>j&1)看有没有走到j
if(((i-(1<<j))>>k)&1)集合i除去节点j后是经过k的
区分起点01
起点初始化
走过所有集合的状态表示
  • 2
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 1024 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值