Loading

枚举子集

枚举子集

发现自己经常记不住这个怎么写,就来写篇题解吧。

首先对于一个 \(n\) 位二进制数 \(x\),求二进制位上都是 \(x\) 的子集的数有哪些,

比如:101的子集有100、001、000。

然后这个东西如果直接 \(2^n\) 次枚举的话,复杂度就爆炸了。

所以可以用一个很厉害的东西,

代码长这样:

for(int i=(x-1)&x;i;i=(i-1)&x)

这个 \(i\) 直接就是枚举出来的子集了。

这个为啥是对的呢?

我的理解是,你普通枚举 \(i\)-- 了之后,可能出现一个地方 \(i\) 有个 \(1\) 但是 \(x\)\(0\)

所以你把 \(i\)\(x\) 与一下,来把这个 \(1\) 消掉就好了。

如果只求一个 \(x\) 的所有子集,那复杂度就是 \(2^n\)

但是如果求 \(1\sim 2^n-1\) 中的数字的所有子集,那这个的复杂度就是 \(3^n\)

(好像是用什么二项式定理求出来的,但是蒟蒻不会qwq)

例题

P3773 [CTSC2017] 吉夫特

发现组合数可以用 \(lucas\) 分解一下,相当于对于 \(2\le i \le k\)\(a_{b_i}\) 都要是 \(a_{b_{i-1}}\) 二进制位上的子集。

直接 \(dp\),设 \(f[i]\) 表示以 \(i\) 结尾的合法子序列个数。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,mod=1e9+7;
int n,ans,f[N];
int main()
{
    scanf("%d",&n);
    for(int i=1,x;i<=n;i++)
    {
        scanf("%d",&x);
        for(int j=(x-1)&x;j;j=(j-1)&x)f[j]=(f[j]+f[x]+1)%mod;
        ans=(ans+f[x])%mod;
    }
    printf("%d",ans);
    return 0;
}
posted @ 2024-12-13 10:25  AvisD  阅读(47)  评论(0)    收藏  举报