蓝桥杯 回路计算(状压DP)

3 篇文章 0 订阅
1 篇文章 0 订阅

蓝桥杯 回路计算(状压DP)

  • 问题网址https://www.lanqiao.cn/problems/1462/learning/?contest_id=73

  • 不了解状压DP是什么的小伙伴可以先去B站看看这个视频
    https://www.bilibili.com/video/BV1Z4411x7Kw/?spm_id_from=333.337.search-card.all.click

  • 楼栋是从0开始编号的。

  • 先讲一个最简单的例子:二维数组F [i] [j],i 可以看做二进制,表示状态,而 j 表示 i 状态下的一种情况。
    假设有5个点,i表示01101,经过了点3、2、0的一种状态,但是顺序是咋样的呢,现在停留在哪个点呢?不知道。那我们再来看j,假设j=2,说明当前停留在2这个点上,而F[13][2]=2,表示什么?就表示01101路径状态下,最后到达点2的情况有2种,不管起点是啥,情况就是2种。这样更不可能存在重复的情况,为什么呢?因为虽然都经过了3、2、0但是它们最终的落脚点不一样,排列的问题,所以肯定不存在重复。

  • 接着我们再来讲一讲状态转移方程的问题。首先要明白一个问题,i状态是递增的,每次新的i的状态的各个情况都是未知的,都是新的状态,需要根据先前的状态来推敲出来。假设i=01101,那我们是不是要求它的各种落脚点情况?答案是显然的。也就是要求F[01101][01000]、F[01101][00100]、F[01101][00001]三个值的大小。(注意实际代码中存的都是10进制,从左到右为F[13][3]、F[13][2]、F[13][0],这边写成二进制好理解一些)《这三个值肯定先前不知道,因为i状态是新的》。怎么求呢?那肯定是利用先前的状态。比如既然要知道求F[01101][01000]这个,是不是可以求F[00101]这个状态下各种情况F[00101][00100]、F[00101][00001]这两种情况的落脚点是否能到点01000,一旦能到达是不是就状态就从00101变成了01101了?而且能到达的情况数不就等于F[00101][00001],也就是落脚点00001与01000存在边或者00100与01000存在边嘛。

  • 总结一下就是来了一个新状态i,得到这个状态i各个可能落脚点的情况,然后就要求逐个分析各个落脚点情况的总数了,确定好一个落脚点j,把i状态中的这个落脚点j去除得到状态(i-j),这个状态中也有多个落脚点情况,只要多个落脚点中与j有边,那是不是状态i落脚点j的情况数是不是就等于状态(i-j) 落脚点x(与j有边) 的情况总数和。

直接看代码吧,讲的有点绕,大家可以先去B站看看有关状压DP的讲解,再结合代码看这道题。注释我尽量写的详细了一点。一起加油。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 21;

int n = 21;
LL ans;
// 存储两栋楼是否有通道的数组。
int st[N][N];
// 0到21个点全1,总共有2^21次方个数,1左移21位就等于2^21次方,刚刚好。
LL f[1 << N][N];

int gcd(int x, int y)
{
    if (x % y == 0)
    {
        return y;
    }
    int res = gcd(y, x % y);
    return res;
}

int main()
{
    // 找到各个楼栋之间的通道
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (gcd(j + 1, i + 1) == 1)
            {
                st[i][j] = st[j][i] = 1;
            }
        }
        // 同楼层之间通道设为0,其实这个也用不到,下面代码不会判断自己到自己是否存在边。
        st[i][i] = 0;
    }

    // 状态1(表示经过了0号楼)落脚点为0,认为是有一种情况,不然全为0,DP怎么弄都是0。
    f[1][0] = 1;

    // 0到21个点全1,总共有2^21次方个数,1左移21位就等于2^21次方,刚刚好。
    for (int i = 0; i < (1 << N); i++)
        // i访问状态下。找它的各个落脚点j情况。
        for (int j = 0; j < N; j++)
        {   
            // 既然是状态i的落脚点,那肯定得在i里面有出现,所以i右移j位为1与上1为1说明存在。
            // 那么经过状态 i 到达 j 的方案数等于从起点不经过点 j(也就是状态i中没有j) 到达全部与 j 点可达的点 k 方案数
            if (i >> j & 1)
            {
                // 找到了其中一个落脚点j,那我就从i里面把落脚点j删去,从删去后的状态(假设状态为i-j)找这个状态是否有落脚点与j相连。
                for (int k = 0; k < N; k++)
                { 
                    // 假设从i删去落脚点j,那么就不会再找j这个点(前面st[j][j]=0),这种情况直接pass了。
                    // 那就从删去的状态i-j中找这个状态的所有落脚点(只有 i >> k & 1 = 1 才是落脚点) 是否与j有边。
                    // 有边就好办了,直接i-j状态有边的落脚点k这个情况总数  先加到 i状态落脚点j这个情况里面
                    // 再找其他的i-j状态下与j有边的落脚点。
                    if (i >> k & 1 && st[j][k])
                    {   
                        // 所有这边为加,因为i-j状态下与j有边的落脚点可能不止一个
                        // i - (1 << j)  表示的就是从i里面把落脚点j删去,也就是状态i-j,再在这个状态找到落脚点k与j有边,那就直接加进去。
                        f[i][j] += f[i - (1 << j)][k];
                    }
                }
            }
        }

    // 因为每个点都能通向0号大楼(从0开始)
    // 那么我们可以认为全部点都经过了,也就是21个位置全为1,就等于(1<<N)-1,这个情况的每个落脚点总计20个落脚点,除了0号楼本身,
    // 代表的意思就是从任意点走完全部21个点到达这20落脚点的总数和就等于回路的总数,因为每个点都能通向0号大楼(从0开始)。
    for (int i = 1; i < N;i++)
        ans += f[(1 << N) - 1][i];

    // 有一个有意思的情况,为什么不直接输出f[(1<<N)-1][0]呢?因为它的起始点是0,不能自己又走到0,DP求得是非回路的。
    // 正因为起始点是1,那么必须会经过1,所有不经过1的状态,那么它的各种情况都是0,这与初值的设定有关。

    cout << f[5][0] << f[14][2] << f[14][14] << endl;

    cout << ans << endl;
    // cout << f[(1 << N) - 1][0] << endl;
    return 0;
}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值