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

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

哈密顿图定义

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

最短Hamilton路径

思路

暴力搞的话复杂度为【状压DP】哈密顿回路问题_哈密顿回路显然的NP难题,直接暴力很难搞

状态表示

f(i,j)

集合:所有从0走到j,走过的所有点是i的所有路径

属性:Min

状态计算

枚举倒数第二个点是哪个点,用倒数第二个点来分类

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


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


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

i的意义是一个集合,将这个集合转化为一个数,然后用位移运算来实现操作。

目标【状压DP】哈密顿回路问题_哈密顿回路_05

代码

#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:回路计数

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

代码

状态表示

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

集合:所有从1走到j,走过的所有点是i的所有路径

属性:方案数

状态转移:【状压DP】哈密顿回路问题_哈密顿回路_08

目标:【状压DP】哈密顿回路问题_i++_09

初始化:20个起点为1

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

#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​

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

思路

题目越短,难度越大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的

区分起点0和1
起点初始化
走过所有集合的状态表示