更快的哈密顿路径/哈密顿回路算法

对于哈密顿问题,朴素的做法通常是 O(n!) 或者 O(2nn3) 的简单状压。接下来我们对哈密顿问题的状压做法进行若干优化,做到空间 O(2n),时间 O(2nn)

首先朴素状压设 fS,u,v 表示是否存在一条 uv 的经过的点集为 S 的路径。

01 状态显然非常浪费,可以拿类似 bitset 的技巧优化一下。我们将 v 这一维压成一个数,状态变成 fS,u,对于点 v 的邻域也压成一个数 gv,那么转移的判断条件变成了 gv bitand fS,u0

时间优化到了 O(2nn2),空间到了 O(2nn)

接下来有两种优化路线:

Route 1

给出这道题,此题瓶颈在于判断每一个点的集合是否是一个回路。

回路的特点是起点任意,所以我们钦定集合中最小的那个点永远是起点,扔掉了 u 这一维,接下来按照上述方法优化转移即可。

输出方案需要一定的技巧,如果直接记录每个状态的前驱空间复杂度会多一个 n,这样常数巨大。

正确的做法是用 bitandlowbit 操作每次找出一个当前点的合法前驱。

原题代码:

#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<typename T=int>
T read(){
char c=getchar();T x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
int n,m;
int g[20];
int f[1<<20];
bool cir[1<<20];
int res[20];
int lis[20],rk;
void output(int s,int x){
puts("Yes");
for(int i=0;i<n;++i){
if(s>>i&1) continue;
res[i]=__builtin_ctz(g[i]&s);
}
while(s^=(1<<x)){
lis[rk++]=x;
x=__builtin_ctz(f[s]&g[x]);
}
lis[rk++]=x;
for(int i=1;i<rk;++i) res[lis[i-1]]=lis[i];
res[lis[rk-1]]=lis[0];
for(int i=0;i<n;++i) printf("%d ",res[i]+1);
putchar('\n');
}
int main(){
n=read();m=read();
for(int i=0;i<m;++i){
int u=read()-1,v=read()-1;
g[u]|=(1<<v);
g[v]|=(1<<u);
}
for(int i=0;i<n;++i) f[1<<i]=1<<i;
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s);
for(int i=lb+1;i<n;++i){
if(s>>i&1) continue;
if(f[s]&g[i]) f[s|(1<<i)]|=(1<<i);
}
if(f[s]&g[lb]){
bool fl=1;
for(int i=0;i<n;++i){
if(s>>i&1) continue;
if(g[i]&s) continue;
fl=0;break;
}
if(fl){output(s,__builtin_ctz(f[s]&g[lb]));return 0;}
}
}
puts("No");
return 0;
}

哈密顿回路核心代码:

#include <cstdio>
using namespace std;
int n;
int f[1<<24],g[24];
int lis[24],rk;
char str[30];
int main(){
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%s",str);
for(int j=0;j<n;++j) g[i]|=(str[j]^48)<<j;
}
for(int i=0;i<n;++i) f[1<<i]=1<<i;
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s);
for(int i=lb+1;i<n;++i){
if(s>>i&1) continue;
if(f[s]&g[i]) f[s|(1<<i)]|=(1<<i);
}
}
if(f[(1<<n)-1]&g[0]){
puts("Yes");
int s=(1<<n)-1;
int x=__builtin_ctz(f[s]&g[0]);
while(s^=(1<<x)){
printf("%d ",x);
x=__builtin_ctz(f[s]&g[x]);
}
printf("%d ",x);
putchar('\n');
}
else puts("No");
return 0;
}

上述方法不好做哈密顿路径。下面的方法更具有普遍性。

Route 2

给出这道题

我们发现对于每个起点跑一遍 O(2nn)DP 很浪费。我们只对一个节点 0 跑出以它为起点的路径信息 fS

对于一条哈密顿回路,必须有 ufmasku,vfmaskv,并且 maskumaskv={0,1,,n1},maskumaskv={0}

于是枚举 masku 就可以利用位运算统计出所有点对间是否存在哈密顿路径,自然也判断出了哈密顿回路。

原题代码:

#include <cstdio>
using namespace std;
int n;
int f[1<<24],g[24];
int lis[24],rk;
char str[30];
int res[24];
int main(){
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%s",str);
for(int j=0;j<n;++j) g[i]|=(str[j]^48)<<j;
}
f[1]=1;
for(int s=1;s<(1<<n);s+=2){
if(!f[s]) continue;
for(int i=1;i<n;++i){
if(s>>i&1) continue;
if(f[s]&g[i]) f[s|(1<<i)]|=(1<<i);
}
}
for(int s=1;s<(1<<n);s+=2){
if(!f[s]) continue;
for(int u=0;u<n;++u)
if(f[s]>>u&1) res[u]|=f[((1<<n)-1)^s^1];
}
for(int u=0;u<n;++u){
for(int v=0;v<n;++v)
if(res[u]>>v&1) putchar('1');
else putchar('0');
putchar('\n');
}
return 0;
}
posted @   yyyyxh  阅读(1111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示