给出长度为 n n 的数组 si s i ,对于所有满足下列限制的五元组 (a,b,c,d,e) ( a , b , c , d , e )
1. 1 ≤a, b, c, d, e ≤ n 1 ≤ a , b , c , d , e ≤ n
2. (sa|sb) ( s a | s b ) & sc s c & (sd ( s d ^ se) = 2i s e ) = 2 i for some integer i
3. sa s a & sb = 0 s b = 0
求 ∑fib(sa|sb) ∗ fib(sc) ∗ fib(sd ∑ f i b ( s a | s b ) ∗ f i b ( s c ) ∗ f i b ( s d ^ se) s e ) 。 fibi f i b i 是斐波那契数列的第 i i 项。
1≤n≤106,0≤si<217 1 ≤ n ≤ 10 6 , 0 ≤ s i < 2 17
这题要用到子集卷积和FWT,都完美触及到我的知识盲区,在网上查了点资料,打算写篇博客记录一下。
首先注意到 max(n)>max(si) max ( n ) > max ( s i ) ,所以可以考虑用 ax a x 表示有 ax a x 个 si s i 等于 x x 。
那么原问题大概可以通过以下步骤解决:
1.求 A A 数组使得 Ai=fibi∗∑j∑k[j|k==i A i = f i b i ∗ ∑ j ∑ k [ j | k == i && j∩k==∅]∗aj∗ak j ∩ k == ∅ ] ∗ a j ∗ a k
2.求 B B 数组使得 Bi=fibi∗ai B i = f i b i ∗ a i
3.求 C C 数组使得 Ci=fibi∗∑j∑k[j C i = f i b i ∗ ∑ j ∑ k [ j ^ k==i]∗aj∗ak k == i ] ∗ a j ∗ a k
4.求 D D 数组使得 Di=∑j∑k[j D i = ∑ j ∑ k [ j & k==i]∗Aj∗Bk k == i ] ∗ A j ∗ B k
5.求 E E 数组使得 Ei=∑j∑k[j E i = ∑ j ∑ k [ j & k==i]∗Dj∗Ck k == i ] ∗ D j ∗ C k
1是子集卷积,345都可以 FWT F W T 搞搞。
子集卷积直接做有一种 O(3n) O ( 3 n ) 的做法,于是去学习了一下 O(n2∗2n) O ( n 2 ∗ 2 n ) 的姿势。不过网上貌似没有很多我看的懂的资料。查了很久查到了讲集合并卷积的东西。。仔细想了一下可能做法差不多。。?
集合并卷积是求 fi=∑j∑k[j|k==i]∗aj∗ak f i = ∑ j ∑ k [ j | k == i ] ∗ a j ∗ a k ,也就是两个子集交集可以不为空。
考虑这样一个二维上的问题:求 fi=∑max(r1,c1)==i∑max(r2,c2)==iar1,c1∗ar2,c2 f i = ∑ max ( r 1 , c 1 ) == i ∑ max ( r 2 , c 2 ) == i a r 1 , c 1 ∗ a r 2 , c 2
有一种做法是求一次二维前缀和后,将前缀和自己做一次点积,然后沿着对角线做差分就是答案。
对应到求集合并卷积可能也是差不多的道理(强行意念差不多)。做一次高维前缀和之后点积一下然后高维差分就是答案。
子集卷积实际上就是把集合大小相同的集合都放在了一起,每种集合大小都各自跑集合并卷积,点积的时候把不同集合大小的卷起来就行。。
FWT F W T 的证明 YY Y Y 了好久。。最后 xjb x j b 想了一下假装自己会了。。
for(int d=1;d
//x=a[i+j],y=a[i+j+d]
//正变换
//and a[i+j]=x+y
//or a[i+j+d]=x+y
//xor a[i+j]=x+y,a[i+j+d]=x-y
//逆变换
//and a[i+j]=x-y
//or a[i+j+d]=y-x
//xor a[i+j]=(x+y)/2,a[i+j+d]=(x-y)/2
}
对于 and a n d ,考虑新增的位, f0 f 0 和 f1 f 1 都被加到 f′0 f 0 ′ 上,也就是 f′0=f0+f1,f′1=f1 f 0 ′ = f 0 + f 1 , f 1 ′ = f 1 ,点积之后就是
h0=f′0∗g′0=(f0+f1)∗(g0+g1)=f0∗g0+f0∗g1+f1∗g0+f1∗g1 h 0 = f 0 ′ ∗ g 0 ′ = ( f 0 + f 1 ) ∗ ( g 0 + g 1 ) = f 0 ∗ g 0 + f 0 ∗ g 1 + f 1 ∗ g 0 + f 1 ∗ g 1
h1=f′1∗g′1=f1∗g1 h 1 = f 1 ′ ∗ g 1 ′ = f 1 ∗ g 1
显然 h0 h 0 多了一项 f1∗g1 f 1 ∗ g 1 ,因为 1 1 & 1==1 1 == 1 ,而这一项恰好等于 h1 h 1 ,在逆变换中减去即可。将每一位多出的答案依次减掉之后最后就是所求。
#include
using namespace std;
typedef long long ll;
const int N=1<<17;
const int MOD=1000*1000*1000+7;
int n,inv2,a[N],fib[N],bits[N],b[18][N],c[18][N],A[N],B[N],C[N];
inline void read(int&x){char c;while((c=getchar())<'0'||c>'9');x=c-'0';while((c=getchar())>='0'&&c<='9')x=x*10+c-'0';}
inline void add(int&x,int y){x=x+yx+y:x+y-MOD;}
inline int he(int x,int y){return x+yx+y:x+y-MOD;}
int Pow(int x,int y){int t=1;for(;y;y>>=1,x=1LL*x*x%MOD)if(y&1)t=1LL*t*x%MOD;return t;}
int main(){
int i,j,d,S;
read(n);
inv2=Pow(2,MOD-2);
for(i=1;i<=n;++i)read(j),++a[j];
for(fib[1]=1,i=2;i1],fib[i-2]);
for(i=1;i>1]+(i&1);
for(i=0;ifor(i=0;i<18;++i){
for(j=0;j<17;++j)
for(S=0;Sif(1<1<for(i=0;i<18;++i){
for(j=0;j<=i;++j){
for(S=0;S1LL*b[j][S]*b[i-j][S]%MOD);
}
}
}
for(i=0;i<18;++i){
for(j=0;j<17;++j){
for(S=1;Sif(1<1<for(i=0;i1LL*c[bits[i]][i]*fib[i]%MOD;
for(i=0;i1LL*a[i]*fib[i]%MOD,C[i]=a[i];
for(d=1;d1){
for(i=0;i1)){
for(j=0;jint x=C[i+j],y=C[i+j+d];
C[i+j]=he(x,y);
C[i+j+d]=he(x,MOD-y);
}
}
}
for(i=0;i1LL*A[i]*B[i]%MOD,C[i]=1LL*C[i]*C[i]%MOD;
for(d=1;d1){
for(i=0;i1)){
for(j=0;jint x=C[i+j],y=C[i+j+d];
C[i+j]=1LL*(x+y)*inv2%MOD;
C[i+j+d]=1LL*(x-y+MOD)*inv2%MOD;
}
}
}
for(i=0;i1LL*C[i]*fib[i]%MOD;
for(d=1;d1){
for(i=0;i1)){
for(j=0;jfor(i=0;i1LL*A[i]*C[i]%MOD;
for(d=1;d1){
for(i=0;i1)){
for(j=0;jint ans=0;
for(i=0;i<17;++i)add(ans,A[1<printf("%d\n",ans);
return 0;
}