hihocoder Hamiltonian Cycle(记忆化搜索+剪枝)

本文介绍了如何使用记忆化搜索和剪枝技术解决有向图中哈密顿回路数量的问题。针对n<=12的限制,通过DFS枚举路径并判断是否回到起点。利用位运算优化状态判断,降低时间复杂度,避免超时。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:点击打开链接

题意描述:

给定一个有向图,求图中哈密顿回路的数量。

哈密顿回路,具体到本题中即从某一个点开始经过所有的点一次后再回到该点的不同路径数。对于这个不同需要注意两点:

  1. 如果我们将路径经过的点按顺序写下,比如当n=3时,若存在123和231。此时,我们认为这两条路径是同一条哈密顿回路。而123和213则是不同的哈密顿回路。

  2. 若两个点之间有多条边,经过不同的边的路径仍然看作同一条哈密顿回路。不同哈密顿回路只和经过的点有关。因此对于多条边的情况我们可以将其合并为一条边来考虑。

对于哈密顿回路,一个简单的想法就是枚举所有可能的路径,判定这个路径是否存在。即时间复杂度为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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值