题目链接:点击打开链接
题意描述:
给定一个有向图,求图中哈密顿回路的数量。
哈密顿回路,具体到本题中即从某一个点开始经过所有的点一次后再回到该点的不同路径数。对于这个不同需要注意两点:
-
如果我们将路径经过的点按顺序写下,比如当n=3时,若存在123和231。此时,我们认为这两条路径是同一条哈密顿回路。而123和213则是不同的哈密顿回路。
-
若两个点之间有多条边,经过不同的边的路径仍然看作同一条哈密顿回路。不同哈密顿回路只和经过的点有关。因此对于多条边的情况我们可以将其合并为一条边来考虑。
对于哈密顿回路,一个简单的想法就是枚举所有可能的路径,判定这个路径是否存在。即时间复杂度为O(n!)。而题目给定的数据范围为:n <= 12,所以最大可能的枚举次数为12! = 479,001,600。
极限的数据不到5亿,所以我们可以考虑使用暴力来枚举所有的哈密顿回路。直接采用DFS枚举我们的每一步,最后判定是否走回到起点。
解题思路:记忆花搜索+剪枝
分析:显然如果按照上述方法直接暴搜很容易超时,分析题意我们可以采用记忆化搜索,记录dp[i][j],i表示到达第i的节点,j表示已经走过那些点
同时由于结点数比较少,所以我们可以采用位运算,这样判断是否已经走过所有定点可以在o(1)的时间内完成
代码:
#include <cstdio>
#include <cstring>
#define MAXN 13
using namespace std;
bool g[MAXN][MAXN];///去重边
bool vv[MAXN][1<<12];///vv[i][j]这种状态是否已经访问过,记忆化搜索使用
int dp[MAXN][1<<12];///dp[i][j]状态表示到达点i时所有经过图中点的状态j的方案数
int head[MAXN],tol;
struct node{
int to,next;
}edge[210];
void addEdge(int x,int y){
edge[tol].to=y;edge[tol].next=head[x];head[x]=tol++;
}
int vis,flag;
int n,m,ans;
void dfs(int rt){
if(vv[rt][vis]) { ans+=dp[rt][vis];return;}///如果已经访问过,直接加上dp即可
else {
vv[rt][vis]=true;
for(int k=head[rt];k!=-1;k=edge[k].next){
int i=edge[k].to;
if(g[rt][i]){
if(i==1&&vis==(1<<n)-1){///判断形成哈密顿回路,位运算使O(n)变为o(1)
dp[rt][vis]++; ans++; continue;
}
if(!(vis&(1<<(i-1)))&&((vis&flag)!=flag)){///剪枝,如果剩下的点都不能到达1则剪掉
vis=vis|(1<<(i-1));
dfs(i);
dp[rt][vis^(1<<(i-1))]+=dp[i][vis];///更新状态
vis=vis^(1<<(i-1));
}
}
}
}
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
memset(g,false,sizeof(g));
memset(head,-1,sizeof(head));
tol=0;
int x,y;
flag=0;
for(int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
if(!g[x][y]){///去除重边
g[x][y]=true;
addEdge(x,y);
if(y==1)
flag|=(1<<(x-1));///记录哪些点可以回到点1,用于剪枝
}
}
memset(vv,false,sizeof(vv));
memset(dp,0,sizeof(dp));
ans=0;
vis=1;
dfs(1);
printf("%d\n",ans);
}
return 0;
}