• CF327E Axis Walking


    题目传送门

    一、题目大意

    给你一个长度为\(n\)(\(1<=n<=24\))的正整数序列\(S\),再有\(k\)(\(0<=k<=2\))个正整数。

    求有多少种\(S\)的排列方式使得其任意前缀和不会成为那\(k\)个数里的任意一个。 答案对\(1e9+7\)取模。

    二、题目解析

    这题很明了,一看就是状压\(dp\)

    对于那些前缀和我们可以累加取得

    不妨设\(sum[i]\)表示当状态为\(i\)时的前缀和

    那么显然有\(sum[i]=∑i\)中为\(1\)的位置的\(sum\)

    对于那些\(sum[i]\)给定的数的状态显然要舍去

    那么不妨再设\(f[i]\)为状态为\(i\)时的方案数

    类似于\(sum\)的转移,显然有\(f[i]=∑i\)中为\(1\)的位置的\(f\)
    发现这两者都需要访问那些状态\(i\)中的\(1\)的位置

    那么对于这些状态\(i\)中那些为\(1\)的位置如何访问呢?

    我们当然不能每次都让\(i>>1\)然后再判断这一位是否为\(1\),因为这样太慢了

    所以我们需要快速地访问那些\(1\)的位置

    这时需要用到神奇(毒瘤)的位运算了

    我们发现当\(i \& \sim lowbit(i)\)时恰好能达到这一点

    它相当于把\(i\)最低位\(1\)以后的部分全部消去了,相当于加速了我们的查找过程

    于是乎,这题就与普通的状压\(dp\)无异了

    三、完整代码

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    const int MOD = 1e9 + 7;        //取模
    const int N = 1 << 24;          //最多24个数字
    int n;                          //实际n个数字
    int k;                          //k个不允许出现的数字
    int full;                       //将所有位置全部为1时的拉满状态,对应的十进制数
    unordered_set<int> no_permit;   //有哪些数字是不允许出现在前缀和中的
    int f[N];                       //f[i]表示状态为i时的方案数
    LL sum[N];                      //sum[i]表示当状态为i时的前缀和
    
    //一般状态压缩DP,n<=12,这里的n<=24,看来暴力的状态压缩要挂~
    int lowbit(int x) {
        return x & (-x);
    }
    
    int main() {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> sum[1 << i]; //按二进制位置映射的十进制数来存储(状态压缩式的存储方法)
        //前缀和不允许出现的数字
        cin >> k;
        int x;
        for (int i = 0; i < k; i++)cin >> x, no_permit.insert(x);
    
        //状态为0时,表示一个全部都不选择,那么前缀和是0,肯定不在给定的k个正整数之中。因为正整数都大于0,这是一种唯一方案。
        f[0] = 1, full = (1 << n) - 1;//满状态
        //枚举现在的状态
        for (int i = 1; i <= full; i++) {
            //每次新的状态(sum[i]) = 仅有末尾1的状态 + 去掉末尾1的状态,而二者都在先前求过了,可以O(1)的求出sum[i] 的值
            sum[i] = sum[i & ~lowbit(i)] + sum[lowbit(i)];
            //sum[i] += sum[i - 1]; 这种传统方法是不可以的,因为sum数组并不是按一个个数字按次序保存的,而是按二进制数位方式保存的
            //这所以这样保存,是因为状态压缩DP,是需要枚举每一个数位,每个数位与传统方式的前缀和不符,需要特殊记忆一下这个方法
            //如果前缀和等于限定的数,那么是不合法的
            if (no_permit.count(sum[i]))continue;
    
            //快速找到所有位置为1的
            for (int j = i; j; j -= lowbit(j))//枚举可能的上一次状态
                f[i] = (f[i] + f[i & ~lowbit(j)]) % MOD; //去掉j位为1的 i & ~lowbit(j)
        }
        printf("%d\n", f[full]);
        return 0;
    }
    
  • 相关阅读:
    No.1
    JS二叉树的操作
    JS实现快排
    BOM中的各种height
    innerHTML outerHTML innerText value 区别
    【转载】JS中DOM操作汇总
    【转载】轻松理解JS闭包
    【转载】JavaScript模块入门
    【转载】JavaScript模块简介
    【转载】浏览器缓存详解:expires cache-control last-modified
  • 原文地址:https://www.cnblogs.com/littlehb/p/15878731.html
Copyright © 2020-2023  润新知