【状压DP】哈密顿回路问题
lzq同学在我准备午睡的时候发了一道蓝桥杯的题目给我,是哈密顿回路的。第一次看的时候是想DFS+双向搜索优化减小搜索树规模,然后写烂了(如果有大佬用搜索优化写出来了麻烦教教我这蒟蒻)。
后来请教了ph大佬,说是状压dp。确实,以前蓝书上见到过一道最短哈密顿路径的状压DP,所以学了点相关知识
哈密顿图定义
哈密顿图
通过图中所有顶点一次且仅一次的通路称为哈密顿通路。
通过图中所有顶点一次且仅一次的回路称为哈密顿回路。
具有哈密顿回路的图称为哈密顿图。
具有哈密顿通路而不具有哈密顿回路的图称为半哈密顿图。
最短Hamilton路径
思路
暴力搞的话复杂度为显然的NP难题,直接暴力很难搞
状态表示
f(i,j)
集合:所有从0走到j,走过的所有点是i的所有路径
属性:Min
状态计算
枚举倒数第二个点是哪个点,用倒数第二个点来分类
i的意义是一个集合,将这个集合转化为一个数,然后用位移运算来实现操作。
目标:
代码
#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:回路计数
代码
状态表示
集合:所有从1走到j,走过的所有点是i的所有路径
属性:方案数
状态转移:
目标:
初始化: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的 |
区分起点0和1
起点初始化
走过所有集合的状态表示