状态压缩——hihoCoder 1087

  • 题目链接: https://hihocoder.com/problemset/problem/1087

  • 题意: 给出一个N个点,M条边的有向图,求其中有多少条哈密顿回路

  • 分析:这道题非常直接的做法就是DFS搜索了,我们可以从任一顶点出发,不走重复点,若能回到顶点,那么哈密顿回路数量+1。但是直接暴力搜会超时,所以需要加上一个位运算的优化。

  • 位运算搜索:

  • 将邻接链表压缩成一个二进制串,第 i 位为1 则表示这个点能到达点 i,为 0 则表示不能到达。

  • 用一个二进制串表示状态,第 i 位为1则标识未访问过

  • 搜索代码:

int n,m;
int loc[1<<13]; //存储只含有一个1的二进制串的1在第几位上
int edge[MaxN];
int ans = 0;
void dfs(int u, int stat)
{
    if( !stat ) ans += (edge[u]&1);
    else
    {
        int rest = stat & edge[u];
        while(rest)
        {
            int v = rest&(-rest);
            dfs(loc[v], stat-v);
            rest -= v;
        }
    }
}
void solve()
{
    dfs(1, (1<<n)-2);
    print(ans);
    cout << endl;
}

int main()
{
    scan(n);scan(m);
    for(int i=0;i<n;i++) loc[1<<i] = i+1;
    while(m--)
    {
        int a,b;
        scan(a);scan(b);
        edge[a] |= 1 << (b-1);
    }
    solve();
    system("pause");
}
  • 在搜索中我们发现,1-2-3-4 和 1-3-2-4 对应的都是 dfs(4, stat)这个stat也都是一样的,像这种类似的情况都是做了重复的计算,所以我们可以将它们记录下来,变成记忆化搜索:

  • 状态:DP[i][stat]表示搜索到第 i 个节点,且 N个节点的访问状况为一个二进制串 stat

  • 转移方程: 如果上一个节点 j 可以到达当前节点 i 且当前状态 stat 表示已经访问过第 i 个节点,且 i!=j 那么:

if( j!=i && (edge[j]&(1<<(i-1))) &&  (stat&(1<<(i-1)))==0 ) 
     DP[i][stat] += DP[j][stat+(1<<(i-1))];
  • for循环遍历顺序,由于我们是从stat 有N-1个1(即访问过1节点,假定1节点为起点),那么我们需要从按含有1的个数从N-1到0来搜索stat,对于每一个stat,我们再两层for循环遍历上一个节点和当前节点的组成情况(此处两个for循环的顺序可调换)

  • DP代码:

int n,m;
int nums[1<<MaxN];
int edge[MaxN];
int DP[MaxN][(1<<MaxN)];
void init()
{
    for(int i=0;i<(1<<n);i++)
    {
        int tmp = 0;
        int x = i;
        while(x)
        {
            tmp ++;
            x&=(x-1);
        }
        nums[i] = tmp;
    }
}

void solve()
{
    init();
    DP[1][(1<<n)-2] = 1;
    for(int t=n-1;t>=0;t--)
    {
        for(int stat=0;stat<(1<<n);stat++)
        {
            if(nums[stat] == t)
            {
                //cout << bitset<8>(stat) << endl;
                for(int j=1;j<=n;j++)
                {
                    for(int i=1;i<=n;i++)
                    {
                        if( j!=i && (edge[j]&(1<<(i-1))) &&  (stat&(1<<(i-1)))==0 ) 
                            DP[i][stat] += DP[j][stat+(1<<(i-1))];
                    }
                } 
            }  
        }
    }
    int ans = 0;
    for(int i=1;i<=n;i++)
    {
        if(edge[i]&1)ans += DP[i][0];
    }
    print(ans);
    cout << endl;
}

int main()
{
    scan(n);scan(m);
    while(m--)
    {
        int a,b;
        scan(a);scan(b);
        edge[a] |= 1 << (b-1);
    }
    solve();
    system("pause");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值