BZOJ 4762: 最小集合

来源:互联网 发布:妙味云课堂js视频下载 编辑:程序博客网 时间:2024/04/27 10:33

Description

定义一个非空集合是合法的,当且仅当它满足以下两个条件。
1、集合内所有元素and和为0
2、它的非空子集中仅有它本身满足1
给出一个集合S,求它的合法非空子集数。

Input

第一行一个正整数n,表示|S|
第二行n个非负整数ai,表示集合内的元素。
n≤1000,ai<1024

Output

一个整数,表示S的合法非空子集数。答案可能很大,请mod 1e9+7之后输出。

Sample Input

4

1 2 4 4

Sample Output

5

样例解释:满足条件的集合为{1,2},{1,4},{1,4},{2,4},{2,4}

分析

转自栋爷的博客

先把给定集合所有数取反。
比如有效位数是4位,1101就变成0010。
那么问题变成,所有元素or和为1023,而去掉任意一个元素后or和均不为1023。
那么接下来我们来设一个诡异的状态。
因为要知道去掉一个人元素会不会使or和为1023,因此我们前后都要知道。
可以设一个f[i,j,k]表示做完了i个数,前面选择的一些数or和为j,我们希望后面选出的数or和为k。
然而随便推一推都觉得不会转移啊。。
这时赶紧改一下状态,设f[i,j,k]表示做完了i个数,前面选择的一些数or和为j,我们希望后面选出的数or和包含k(什么叫包含?x包含k需满足x&k=k)
设第i+1个数为x。
不选?
f[i+1][j][k]+=f[i][j][k]

选呢?
假设后面部分不包含x时是k’。
我们发现k’需要满足两个条件:
1、k’|x=k(根据状态定义)
2、k’|j|x!=k’|j(如果等了那么就能去掉x了)
满足条件的k’肯定存在一些包含关系,而我么的状态设的也是包含,所以转移会比较方便。
先只考虑满足第一个条件:
f[i+1][j|x][k^(k&x)]+=f[i][j][k]
这个很容易考虑,如果k的某一位有1,x该位也有1,那么k’的这一位可以是0也可以为1。
再去掉满足第一个条件而不满足第二个条件的:
f[i+1][j|x][(k^(k&x))|(x^(x&j))]-=f[i][j][k]
这是个什么意思?先看后面,显然只有x的某一位是1,而j的对应位是0时才有1的贡献,意思就是x会给j的哪些原本没有1的位变成1。
而如果k’的这些位也有1,那么j|k’后,再或x将不变,因此会不满足第二个条件。
然后这个dp就是正确的,初始f[0][0][0]=1,最后答案是f[n][1023][0]。
假设有m位,这样做是n∗4^m
考虑优化吧。我们来证明,如果f[i][j][k]不为0,一定有j包含k。
初始时显然满足。
看第一条转移,f[i+1][j|x][k^(k&x)]+=f[i][j][k]。
假如j包含k,k的某位为0时,k&x的对应位肯定也是0,所以k^(k&x)并不会多1,反而可能少1,但j|x不会少1,因此有j|x包含k^(k&x)。
看第二条转移,f[i+1][j|x][(k^(k&x))|(x^(x&j))]-=f[i][j][k]。
假如j包含k,记k’=k^(k&x)。假如k’某位为1,可以不管它,由上面的结论j|x的这位也会是1。假如k某位为0,k’这位也是0,而x^(x&j)是1,那么最终这位会是1,而因为x^(x&j)的这位是1,x这位必须是1,那么j|x这位也有1了。
这样枚举j后每次只需枚举一个j包含的k,有个快捷的枚举方法见代码。
那么可以证明复杂度降为
n∗3^m

代码

#include <bits/stdc++.h>#define N 1030#define MOD 1000000007using namespace std;int n;int f[2][N][N], tmp;void ADD(int &t, int d){    t += d;    if (t >= MOD) t -= MOD;}int main(){    scanf("%d", &n);    f[0][1023][1023] = 1;    for (int i = 1; i <= n; ++ i)    {        int d;        scanf("%d", &d);         tmp ^= 1;        for (int j = 0; j < 1024; ++ j)        {            for (int k = j; k; k = (k - 1) & j)                f[tmp][k][j] = 0;            f[tmp][0][j] = 0;        }        for (int j = 0; j < 1024; ++ j)        {            for (int k = j; k; k = (k - 1) & j)                if (f[!tmp][k][j])                {                    ADD(f[tmp][k][j], f[!tmp][k][j]);                    ADD(f[tmp][k & d][j & d], f[!tmp][k][j]);                    ADD(f[tmp][k & d][j & (k | d)], MOD - f[!tmp][k][j]);                }            ADD(f[tmp][0][j], f[!tmp][0][j]);            ADD(f[tmp][0][j & d], f[!tmp][0][j]);            ADD(f[tmp][0][j & (0 | d)], MOD - f[!tmp][0][j]);        }    }    printf("%d", f[tmp][0][0]);}
0 0