CodeChef November Challenge2019 Winning Ways (3-FWT)

CodeChef November Challenge2019 Winning Ways (3-FWT)

显然每个把每个数换成其因子个数-1,就能转为一个扩展的\(\text{Nim}\)游戏

每次操作\(1,2,\cdots,k\)堆的\(\text{Nim}\)游戏,其判定方法是:

将每个数二进制分解,对于每个二进制位上分别计算1的个数\(\mod (k+1)\),如果均为0则先手必败

对于这道题\(k=3\),我们考虑将其转为二进制之后的形式累成3进制,然后就能进行3进制按位不进位加法,即类异或

然后问题实际上有非常多的部分需要考虑

Part1 如何求因子个数

一个简单的方法是枚举\([1,\sqrt n]\)判断是否整除,复杂度过高

对于\(n=\prod p_i^{c_i}\)(\(p_i\)为质数),其因子个数为\(\prod (c_i+1)\)

由这个式子对于\(n\)进行质因数分解,枚举\([1,\sqrt n]\)中的质数,复杂度为\(O(\pi(\sqrt n))=O(\frac{\sqrt n}{\log n})\),这个应该够了?

然后是一个常规套路型的分解方法:

先对于\([1,n^{\frac{1}{3}}]\)的质数筛\(n\),剩余的部分只有3种情况

1.\(n\)被筛成1了

2.\(n\)被筛到只剩一个质数,可以用\(\text{Miller_Rabin}\)算法快速判断,可以参考

3.\(n\)仍然是若干质数的乘积,此时质因子必然\(>n^{\frac{1}{3}}\),因此最多只有两个

那么只需要判断\(n\)是否是完全平方数即可

总复杂度为\(O(w\cdot \log n+\frac{n^{\frac{1}{3}}}{\log n})\),其中\(w\)\(\text{Miller_Rabin}\)筛选次数

const int N=2e5+10;
int notpri[N],pri[N],pc;

ll qpow(ll x,ll k=P-2,int P=::P) {
    ll res=1;
    for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    return res;
}
int Trans(int x){ 
    static int buf[20],l=0;
    l=0;
    while(x) buf[++l]=x%2,x/=2;
    int s=0;
    drep(i,l,1) s=s*3+buf[i];
    cmax(ma,s);
    return s;
}

int Miller_Rabin(int x) {
    if(x<N) return !notpri[x];
    if(x==2) return 1;
    if(x<=1 || ~x&1) return 0;
    ll s=0,t=x-1;
    while(~t&1) s++,t>>=1;
    rep(i,1,4) {
        ll a=pri[rand()%pc+1],b=qpow(a,t,x),c;
        rep(j,1,s) {
            c=1ll*b*b%x;
            if(c==1 && b!=1 && b!=x-1) return 0;
            b=c;
        }
        if(b!=1) return 0;
    }
    return 1;
}
int CheckSqr(int n){
    int y=round(sqrt(n));
    return y*y==n;
}

int Count(int n){
    int res=1;
    for(int i=1;i<=pc && pri[i]*pri[i]*pri[i]<=n;++i) if(n%pri[i]==0) {
        int c=0;
        while(n%pri[i]==0) n/=pri[i],c++;
        res*=c+1;
    }
    if(n==1) return res;
    if(CheckSqr(n)) return res*3;
    if(Miller_Rabin(n)) return res*2;
    return res*4;
}

void Init(){
    rep(i,2,N-1) if(!notpri[i]) {
        pri[++pc]=i;
        for(int j=i+i;j<N;j+=i) notpri[j]=1;
    }
}

Part2 快速计算答案

\(10^9\)以内的数,最大因子个数为\(1334\),这个数为\(931170240\)

转成二进制之后最多包含\(11\)位,三进制下最大为\(3^{11}-1=177146\),令这个上界为\(M\)

一种非常暴力的方法就是直接枚举,\(NM\)计算每次选择一个数,复杂度为\(O(NMK)\),应该可以通过\(N\leq 12\)的数据

一个比较浅显的优化可以用快速幂维护乘法,复杂度为\(O(M^2\log K)\)

由于是3进制类异或,接下来考虑用\(\text{3-FWT}\)优化乘法,可以参考

模数为\(P=10^9+7\),不存在整数\(3\)阶单位根,因此要用类似拆系数\(\text{FFT}\)方法做

复杂度为\(O(M\log M\log K)\),似乎已经比较小了,但是常数非常大,应该难以通过

int R;// R为上界
struct Cp{
    db x,y;
    Cp(){}
    Cp(db x,db y):x(x),y(y) {}
    Cp operator + (const Cp t){ return Cp(x+t.x,y+t.y); }
    Cp operator - (const Cp t){ return Cp(x-t.x,y-t.y); }
    Cp operator * (const Cp t){ return Cp(x*t.x-y*t.y,x*t.y+y*t.x); }
} A[N],B[N],C[N],D[N];
Cp w[30];

int Add(int x,int y) {
    static int A[20],B[20];
    rep(i,0,19) A[i]=x%3,x/=3;
    rep(i,0,19) B[i]=y%3,y/=3;
    int ans=0;
    drep(i,19,0) ans=ans*3+((A[i]+B[i])%3);
    return ans;
}

void FWT(Cp *a,int f) {
    for(int i=1;i<R;i*=3) {
        for(int l=0;l<R;l+=i*3) {
            for(int j=l;j<l+i;++j) {
                static Cp t[3];
                if(f==1) {
                    t[0]=a[j]+a[j+i]+a[j+i*2];
                    t[1]=a[j]+w[1]*a[j+i]+w[2]*a[j+i*2];
                    t[2]=a[j]+w[2]*a[j+i]+w[1]*a[j+i*2];
                } else {
                    t[0]=a[j]+a[j+i]+a[j+i*2];
                    t[1]=a[j]+w[2]*a[j+i]+w[1]*a[j+i*2];
                    t[2]=a[j]+w[1]*a[j+i]+w[2]*a[j+i*2];
                }
                a[j]=t[0],a[j+i]=t[1],a[j+i*2]=t[2];
                if(f==-1) {
                    rep(d,0,2) {
                        a[j+i*d].x/=3,a[j+i*d].y/=3;
                    }
                }
            }
        }
    }
}

const int S=(1<<15)-1;

#define FWTs

struct Poly{ 
    int a[N];
    Poly operator * (const Poly __) const {
        Poly res;
        // 拆系数,任意模数3-FWT
#ifdef FWTs
        rep(i,0,R-1) A[i]=Cp((a[i]&S),(a[i]>>15)),B[i]=Cp((a[i]&S),0);
        rep(i,0,R-1) C[i]=Cp((__.a[i]&S),(__.a[i]>>15));
        FWT(A,1),FWT(B,1),FWT(C,1);
#define E(x) ((ll)(x+0.5))%P
        rep(i,0,R-1) A[i]=A[i]*C[i];
        rep(i,0,R-1) B[i]=B[i]*C[i];
        FWT(A,-1),FWT(B,-1);
        rep(i,0,R-1) {
            ll a=E(B[i].x),b=E(A[i].y),c=E(B[i].x-A[i].x);
            res.a[i]=(a+1ll*b*(S+1)+1ll*c*(S+1)*(S+1))%P;
        }
#else
        rep(i,0,R-1) res.a[i]=0;
        rep(i,0,R-1) if(a[i]) rep(j,0,R-1) if(__.a[j]) {
            int k=Add(i,j);
            res.a[k]=(res.a[k]+1ll*a[i]*__.a[j])%P;
        }
#endif
        return res;
    }
} x,res;



ll qpow(ll x,ll k=P-2) {
    ll res=1;
    for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    return res;
}

int main(){
    rep(i,2,N-1) if(!notpri[i]) {
        pri[++pc]=i;
        for(int j=i+i;j<N;j+=i) notpri[j]=1;
    }
    w[0]=Cp(1,0);
    rep(i,1,29) w[i]=w[i-1]*Cp(cos(2*Pi/3),sin(2*Pi/3));
    // 复平面3阶单位根

    n=rd(),m=rd();
    rep(i,1,n) x.a[Count(rd())]++;
    for(R=1;R<=ma;R*=3);

    res.a[0]++;
    for(;m;m>>=1) {
        if(m&1) res=res*x;
        x=x*x;
    }
    int ans=0;
    rep(i,1,R-1) ans+=res.a[i],Mod1(ans);
    printf("%d\n",ans);
}

Further

对于形式幂级数多项式,我们知道\(K\)次幂的循环卷积可以直接 \(\text{DFT}\)一次,每一位快速幂,然后\(\text{IDFT}\)

同理的,如果你学习了\(\text{K-FWT}\)就知道这就是一个按\(K\)进制位,每一位分别进行循环卷积,因此也可以用类似的方法做

但是遇到一个非常大的问题就是无法找到模意义下的\(3\)阶单位根(指\(3\not |(P-1)\))

如果用复平面单位根\(\omega_n=cos(\frac{2\pi}{n})+sin(\frac{2\pi}{n})\cdot i\)(\(i=\sqrt {-1})\),无法在计算时保证值域精度

这里由于\(n=3\)比较特殊,发现\(\omega_3=cos(\frac{2\pi}{3})+sin(\frac{2\pi}{3})\cdot i=-\frac{1}{2}+\frac{\sqrt 3}{2}\cdot i\)

\(3\)\(\mod 10^9+7\)下存在二次剩余,因此可以用一个模意义下的复数描述复平面单位根

应该是有通行的单位根求法,会根据\(n\)不同要用更复杂的高维复数描述,但是我并不会.jpg

总复杂度为\(O(M(\log M+\log K))\),分别为进行\(\text{3-FWT}\)以及快速幂的复杂度

Code总览:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef long double db;
typedef pair <int,int> Pii;
#define reg register
#define pb push_back
#define mp make_pair
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
    int s=0,f=0;
    while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}
bool Mbe;
const int N=2e5,P=1e9+7;
const int Quad3=82062379; // 3在Mod P意义下的二次剩余
const db Pi=acos((db)-1);

const int MaxX=931170240;


int n,m,ma,R;
int notpri[N],pri[N],pc;

ll qpow(ll x,ll k=P-2,int P=::P) {
    ll res=1;
    for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    return res;
}
int Trans(int x){ 
    static int buf[20],l=0;
    l=0;
    while(x) buf[++l]=x%2,x/=2;
    int s=0;
    drep(i,l,1) s=s*3+buf[i];
    cmax(ma,s);
    return s;
}

int Miller_Rabin(int x) {
    if(x<N) return !notpri[x];
    if(x==2) return 1;
    if(x<=1 || ~x&1) return 0;
    ll s=0,t=x-1;
    while(~t&1) s++,t>>=1;
    rep(i,1,4) {
        ll a=pri[rand()%pc+1],b=qpow(a,t,x),c;
        rep(j,1,s) {
            c=1ll*b*b%x;
            if(c==1 && b!=1 && b!=x-1) return 0;
            b=c;
        }
        if(b!=1) return 0;
    }
    return 1;
}
int CheckSqr(int n){
    int y=round(sqrt(n));
    return y*y==n;
}

int Count(int n){
    int res=1;
    for(int i=1;i<=pc && pri[i]*pri[i]*pri[i]<=n;++i) if(n%pri[i]==0) {
        int c=0;
        while(n%pri[i]==0) n/=pri[i],c++;
        res*=c+1;
    }
    if(n==1) return res;
    if(CheckSqr(n)) return res*3;
    if(Miller_Rabin(n)) return res*2;
    return res*4;
}

struct Cp{
    int x,y;
    Cp(){}
    Cp(int x,int y):x(x),y(y) {}
    Cp operator + (const Cp t){ 
        Cp res(x+t.x,y+t.y);
        Mod1(res.x),Mod1(res.y);
        return res;
    }
    Cp operator * (const Cp t){ return Cp((1ll*x*t.x+1ll*(P-y)*t.y)%P,(1ll*x*t.y+1ll*y*t.x)%P); }
} A[N]; // 模意义下 模拟复平面单位根
Cp w1,w2; // 3阶单位根及其平方

Cp qpow(Cp x,ll k=P-2) {
    Cp res(1,0);
    for(;k;k>>=1,x=x*x) if(k&1) res=res*x;
    return res;
}

// 下面是展开的FWT式子
void FWT() {
    for(int i=1;i<R;i*=3) {
        for(int l=0;l<R;l+=i*3) {
            for(int j=l;j<l+i;++j) {
                Cp a=A[j]+A[j+i]+A[j+i*2];
                Cp b=A[j]+w1*A[j+i]+w2*A[j+i*2];
                Cp c=A[j]+w2*A[j+i]+w1*A[j+i*2];
                A[j]=a,A[j+i]=b,A[j+i*2]=c;
            }
        }
    }
}

void IFWT() {
    for(int i=1;i<R;i*=3) {
        for(int l=0;l<R;l+=i*3) {
            for(int j=l;j<l+i;++j) {
                Cp a=A[j]+A[j+i]+A[j+i*2];
                Cp b=A[j]+w2*A[j+i]+w1*A[j+i*2];
                Cp c=A[j]+w1*A[j+i]+w2*A[j+i*2];
                A[j]=a,A[j+i]=b,A[j+i*2]=c;
            }
        }
    }
    ll base=qpow(R);
    rep(i,0,R-1) A[i].x=A[i].x*base%P;
}

int main(){
    rep(i,2,N-1) if(!notpri[i]) {
        pri[++pc]=i;
        for(int j=i+i;j<N;j+=i) notpri[j]=1;
    }
    w1=Cp(P-(P+1)/2,1ll*Quad3*(P+1)/2%P);
    w2=w1*w1;

    rep(kase,1,rd()) {
        n=rd(),m=rd();
        rep(i,1,n) A[Trans(Count(rd())-1)].x++;
        for(R=1;R<=ma;R*=3);
        FWT();
        rep(i,0,R-1) A[i]=qpow(A[i],m);
        IFWT();
        int ans=0;
        rep(i,1,R-1) ans+=A[i].x,Mod1(ans);
        rep(i,0,R-1) A[i].x=A[i].y=0;
        printf("%d\n",ans);
    }
}

posted @ 2020-11-11 11:21  chasedeath  阅读(236)  评论(0编辑  收藏  举报