FWT小记

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;
}

Hard Nim

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 的 &

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值