[bzoj4762]最小集合

来源:互联网 发布:经济学教材 知乎 编辑:程序博客网 时间:2024/06/05 01:15

题目描述

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

DP

先把给定集合所有数取反。
比如有效位数是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位,这样做是n4m
考虑优化吧。我们来证明,如果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,有个快捷的枚举方法见代码。
那么可以证明复杂度降为n3m

#include<cstdio>#include<algorithm>#define fo(i,a,b) for(i=a;i<=b;i++)using namespace std;const int maxn=1000+10,N=1023,mo=1000000007;int a[maxn],f[N+10][N+10],g[N+10][N+10];int i,j,k,l,t,n,m,ans,x;int main(){    scanf("%d",&n);    fo(i,1,n) scanf("%d",&a[i]),a[i]^=N;    f[0][0]=1;    fo(i,1,n){        fo(j,0,N){            k=j;            while (1){                g[j][k]=0;                if (!k) break;                k=(k-1)&j;            }        }        x=a[i];        fo(j,0,N){            //if ((x&j)==x) continue;            k=j;            while (1){                (g[j|x][k^(k&x)]+=f[j][k])%=mo;                (g[j|x][(k^(k&x))|(x^(x&j))]-=f[j][k])%=mo;                if (!k) break;                k=(k-1)&j;            }        }        fo(j,0,N){            k=j;            while (1){                (f[j][k]+=g[j][k])%=mo;                if (!k) break;                k=(k-1)&j;            }        }    }    ans=f[N][0];    (ans+=mo)%=mo;    printf("%d\n",ans);}
0 0
原创粉丝点击