FWT小记
转载了从网上搬了一堆笔记后,写了个小结给自己记一下。
转载博文:
1)https://blog.youkuaiyun.com/qq_37136305/article/details/81606170
2)https://blog.youkuaiyun.com/qq_33229466/article/details/78576727
FWT:用于优化逻辑卷积运算。
定义:
长度 len = 2^n 为数组A和B。定义A0为这个A数组的前2^(n-1) (0 ~ 2^(n-1) )项,A1为这个数组的后2^(n-1) (2^(n-1) +1 ~ 2^n )项,那么有A=(A0,A1).数组B同理
定义加法:A+B = (A[0]+B[0], A[1]+B[1],…,A[n−1]+B[n−1])
定义乘法; A∗B=(A[0]∗B[0],A[1]∗B[1],…,A[n−1]∗B[n−1])
定义二元算法符号@: @为 ^ , &, |逻辑运算符
卷积形式:A@B = (∑ i@j=0 A[i]∗B[j],∑ i@j=1 A[i]∗B[j],…,∑ i@j=n−1 A[i]∗B[j])
目的
给出A,B,求出C= A@B。
然后是一大堆结论证明,见上面两位博主的文章吧。
我只放我的模板和题目总结。QAQ。
模板:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
ll inv2;
ll fast_pow(ll a,ll b){
ll res=1;
while(b>0){
if(b&1) res=res*a%mod;
a=a*a%mod,b>>=1;
}
return res%mod;
}
void FWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j] =(x+y)%mod; a[i+j+d]=(x-y+mod)%mod;
//xor ^:a[i+j]=x+y,a[i+j+d]=(x-y+mod)%mod;
//and &:a[i+j]=x+y;
//or |:a[i+j+d]=x+y;
}
}
}
}
void IFWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j]=1LL*(x+y)*inv2%mod;
a[i+j+d]=(1LL*(x-y)*inv2%mod+mod)%mod;
//xor ^:a[i+j]=(x+y)/2,a[i+j+d]=(x-y)/2;
//and &:a[i+j]=x-y;
//or |:a[i+j+d]=y-x;
}
}
}
}
void solve(int a[],int b[],int n){
FWT(a,n);
FWT(b,n);
for(int i=0;i<n;++i){
a[i] = 1LL*a[i]*b[i]%mod;
}
IFWT(a,n);
}
int main(){
inv2 = fast_pow(2,mod-2);
return 0;
}
例题
51 nod 1773
A国是一个神奇的国家。
这个国家有 2n
个城市,每个城市都有一个独一无二的编号 ,编号范围为0~2n-1。
A国的神奇体现在,他们有着神奇的贸易规则。
当两个城市u,v的编号满足calc(u,v)=1的时候,这两个城市才可以进行贸易(即有一条边相连)。
而calc(u,v)定义为u,v按位异或的结果的二进制表示中数字1的个数。
ex:calc(1,2)=2 ——> 01 xor 10 = 11
calc(100,101)=1 ——> 0110,0100 xor 0110,0101 = 1
calc(233,233)=0 ——> 1110,1001 xor 1110,1001 = 0
每个城市开始时都有不同的货物存储量。
而贸易的规则是:
每过一天,可以交易的城市之间就会交易一次。
在每次交易中,当前城市u中的每个货物都将使所有与当前城市u有贸易关系的城市货物量 +1 。
请问 t 天后,每个城市会有多少货物。
答案可能会很大,所以请对1e9+7取模。
解题:
根据贸易规则:
当天转移到下一天:
1)a[x]->a[x] (自己到)
2)a[x]->a[x xor 2^i]
前一天到今天的转移:
1)a[x xor 0]->a[x]
2)a[x xor 2^i]->a[x]
所以可以构造数组b[], b[0]=1, b[1<<i] = 1,
c[k] = ∑ ((i xor x)=k) a[i]*b[x]
便是一次转移的结果,t次转移等价与数组b[]进行t次卷积,快速幂即可。
数据时间有点紧凑,开个输入输出外挂。
AC code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
ll inv2;
template <typename T>
void read(T &x){
x=0; char c=getchar();
int p = 1;
for (;c<'0'||c>'9';c=getchar()) if(c=='-') p=-1;
for (;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x *= p;
}
int num[30];
template <typename T>
void output(T x){
if(!x){
putchar('0');
return;
}
int pos=0;
while(x){
num[pos++]=x%10;
x/=10;
}
while(pos){
putchar(num[--pos]+'0');
}
return ;
}
ll fast_pow(ll a,ll b){
ll res=1;
while(b>0){
if(b&1) res=res*a%mod;
a=a*a%mod,b>>=1;
}
return res;
}
void FWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j] =(x+y)%mod,a[i+j+d]=(x-y+mod)%mod;
}
}
}
}
void IFWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j]=(x+y)*inv2%mod;
a[i+j+d]=((x-y)*inv2%mod+mod)%mod;
}
}
}
}
const int maxn = (1<<20)+100;
int a[maxn],b[maxn];
int main(){
inv2 = fast_pow(2,mod-2);
int n,t;
read(n),read(t);
int up = (1<<n);
for(int i=0;i<up;++i){
read(a[i]);
}
b[0]=1;
for(int i=0;i<n;++i){
b[1<<i]=1;
}
FWT(a,up);
FWT(b,up);
for(int i=0;i<up;++i){
a[i]=a[i]*fast_pow(b[i],t)%mod;
}
IFWT(a,up);
for(int i=0;i<up;++i){
output(a[i]);
putchar((i==up-1)?'\n':' ');
}
return 0;
}
Claris和NanoApe在玩石子游戏,他们有n堆石子,规则如下:
1. Claris和NanoApe两个人轮流拿石子,Claris先拿。
2. 每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。
不同的初始局面,决定了最终的获胜者,有些局面下先拿的Claris会赢,其余的局面Claris会负。
Claris很好奇,如果这n堆石子满足每堆石子的初始数量是不超过m的质数,而且他们都会按照最优策略玩游戏,那么NanoApe能获胜的局面有多少种。
由于答案可能很大,你只需要给出答案对10^9+7取模的值。
思路:
Nim游戏先手必败条件:n堆石子异或和为0。由于石子堆个数不超过m,所以问题转换为求所有不大于m的质数中选出n个异或和为0的组合方案。
所以构造数组a[], p是素数,则a[p]=1其他数字x a[x]=0.
数组a xor a卷积一次后得到的数组 a’满足:a’下标index即为这两个数的异或和,a’[index]值即为方案数.
由此类推: 要求n个数的异或和对a数组取n次幂。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
ll inv2;
ll fast_pow(ll a,ll b){
ll res=1;
while(b>0){
if(b&1) res=res*a%mod;
a=a*a%mod,b>>=1;
}
return res%mod;
}
void FWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j] =(x+y)%mod,a[i+j+d]=(x-y+mod)%mod;
}
}
}
}
void IFWT(int a[],int n){
for(int d=1;d<n;d<<=1){
for(int m=d<<1,i=0;i<n;i+=m){
for(int j=0;j<d;++j){
ll x=a[i+j],y=a[i+j+d];
a[i+j]=(x+y)*inv2%mod;
a[i+j+d]=(x-y+mod)*inv2%mod;
}
}
}
}
const int maxn = 100000 + 100;
bool isprime[maxn];
int prime[maxn],countp;
int a[maxn];
void init(){
memset(isprime,true,sizeof(isprime));
isprime[0]=isprime[1]=0;
countp=0;
for(int i=2;i<=50000;++i){
if(isprime[i]){
prime[++countp]=i;
}
for(int j=1;j<=countp&&i*prime[j]<=50000;++j){
isprime[i*prime[j]]=0;
if(!(i%prime[j])) break;
}
}
}
int main(){
init();
inv2 = fast_pow(2,mod-2);
int n,m;
while(~scanf("%d%d",&n,&m)){
int len=1;
while(len<=m) len<<=1;
for(int i=0;i<len;++i){
a[i]=(i>m)?0:isprime[i];
}
FWT(a,len);
for(int i=0;i<len;++i){
a[i] = fast_pow(a[i],n);
}
IFWT(a,len);
printf("%d\n",a[0]);//下标对应异或值,a[0]即为最终所求
}
return 0;
}
OneDay 同学最近很生气,因为他又 FST 了三题。
一气之下他决定把室友 & 了。他有 n
个室友,从 1 到 n 编号,第 i (1≤i≤n) 个室友有一个生命值 ai。
如果能选出一组 t1,t2,…,tk (1≤t1<t2<…<tk≤n) 使得 at1&at2&…&atk=0 (1≤k≤n
),他就会很开心。他能选出多少不同组的室友呢?由于答案很大,请输出答案对 10^9+7取模之后的结果。
思路(官方解释 ):
首先高维前缀和求出有多少个数的二进制是某数x 二进制的超集。
再这个值为bx ,则应有2^bx -1 个方案交起来为x 的超集。
然后根据二进制位1 的个数的奇偶性对方案数进行容斥原理。
日后再说
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1 << 20;
const int mod = 1e9 + 7;
ll a[maxn], p[maxn];
int main(){
int n,tmp;
scanf("%d", &n);
p[0] = 1;
for (int i = 1; i < maxn; ++i) p[i] = (p[i - 1] * 2) % mod;
for (int i = 0; i < n; ++i){
scanf("%d", &tmp);
++a[tmp];
}
for (int i = 0; i < 20; i++)
for (int j = 0; j < maxn; ++j)
if (j & (1 << i)) (a[j ^ (1 << i)] += a[j]) %= mod;
ll ans = 0;
for (int i = 0; i < maxn; ++i){
int t = __builtin_parity(i) ? -1 : 1;
ans = (ans + t * p[a[i]] + mod) % mod;
}
printf("%lld\n", ans);
}