1. 程式人生 > >【bzoj4762】【JZOJ5151】最小集合 題解

【bzoj4762】【JZOJ5151】最小集合 題解

轉自 AwD! 的部落格

題目大意

定義一個非空集合是合法的,當且僅當它滿足以下兩個條件。
1、集合內所有元素 and 和為 0
2、它的非空子集中僅有它本身滿足 1
給出一個集合 S,求它的合法非空子集數。

(這裡的集合都是可重的)
n<=1000, 0<=a[i]<1024

題解

一個 AND 和為 0 的集合 S 合法,當且僅當它所有大小為 |S1| 的集合 AND 和都不為 0

也就是說有 |S| 個限制,每個限制為某個集合的 AND 和不為 0
如果令 SaS 刪去 a 這個元素所形成的集合,令 f(S)S 集合中所有元素的 AND 和,
那麼答案就是

S[(f(S)=0)aS(f(Sa)0)]

這玩意是很難直接 dp 的,因此容斥這些限制
也就是說要去求補集:求某些限制同時不合法的方案數

於是有這麼一個式子:[aS(f(Sa)0)]=S'S[aS'(f(Sa)=0)](1)|S'|
注意到 [aS'(f(Sa)=0)]=[ORaS'f(Sa)=0]
也就是如果某些限制同時不滿足的話,那麼如果把所有對應集合的AND和 按位或起來的和為 0

因此答案就等於 SS'S[(f(S)=0)ORaS'f(Sa)=0](1)|S'|
暴力的話列舉集合 S,列舉 S 的子集 S' 就可以算貢獻了

如果要 DP 的話只需要記錄 S

的 AND 和,S'ORaS'f(Sa),對應的貢獻的話就可以 DP 了

fi,k,j 為 考慮了前 i 個數,前者的值為 k,後者的值為 j 的答案

若第 i+1 個數為 d
如果不選第 i+1 個數,則有:fi,k,jfi+1,k,j
如果 S 中選第 i+1 個數,但 S' 中不選,則有:fi,k,jfi+1,k&d,j&d
如果 S 中選第 i+1 個數,且 S' 中也選,則有:fi,k,jfi+1,k&d,k|j&d
初始狀態為 f0,1023,1023=1
最終答案 ans=fn,0,0

程式碼

//from rzO_KQP_Orz
#include<cstdio> #include<cstring> #define fo(i,a,b) for(int i=a;i<=b;i++) using namespace std; typedef long long LL; const int maxn=1005, maxa=1030; const LL mo=1e9+7; int n,a[maxn]; LL f[2][maxa][maxa]; //先k後j可以定址優化 int main() { scanf("%d",&n); fo(i,1,n) scanf("%d",&a[i]); f[0][1023][1023]=1; int p=0; fo(i,0,n-1) { fo(k,0,1023) { for(int j=k; j; j=(j-1)&k) f[p^1][k][j]=0; f[p^1][k][0]=0; } fo(k,0,1023) { for(int j=k; j; j=(j-1)&k) { (f[p^1][k][j]+=f[p][k][j])%=mo; (f[p^1][k&a[i+1]][j&a[i+1]]+=f[p][k][j])%=mo; f[p^1][j|k&a[i+1]][j&a[i+1]]-=f[p][k][j]; } (f[p^1][k][0]+=f[p][k][0])%=mo; } p^=1; } printf("%lld\n",(f[p][0][0]+mo)%mo); }