[航海协会]逆天题

这篇博客介绍了如何使用生成函数解决模运算后的子集和问题。通过将生成函数取模并利用傅里叶变换计算平方和,得出子集和模n的平方和的解决方案。博主详细解释了如何进行傅里叶变换和逆变换,并提出了Pollard_rho算法用于快速分解大整数。最后,给出了C++代码实现。

逆天题

题目概述

在这里插入图片描述
在这里插入图片描述

题解

首先,我们考虑这东西该怎么用生成函数进行表示。
单纯子集和的生成函数显然就是∏i=1mn(1+xi)\prod_{i=1}^{mn}(1+x^i)i=1mn(1+xi),如果要求出将子集和取模nnn后得到的fif_ifi的话。
有一个经典的技巧就是给整个生成函数取模上一个xn−1x^n-1xn1的话,就可以将所有的xkn+bx^{kn+b}xkn+b的系数叠加到xbx^bxb上面。
这样,我们就能够求出fff的生成函数了,也就是F=∏i=0n−1(1+xi)m(modxn−1)F=\prod_{i=0}^{n-1}(1+x^{i})^m \pmod{x^n-1}F=i=0n1(1+xi)m(modxn1)

那么我们答案的∑f2\sum f^2f2又该怎么求了。
可以观察到这相当于一个子集和减去另一个子集和模nnn000的方案数。
显然,我们子集和的函数是高度对称的,也就是说,减去一个子集需要乘的生成函数就是加上它的生成函数。
所以我们的答案为[x0]∏i=0n−1(1+xi)2m(modxn−1)[x^0]\prod_{i=0}^{n-1}(1+x^i)^{2m} \pmod{x^n-1}[x0]i=0n1(1+xi)2m(modxn1)

好的,我们又该怎么计算这东西呢?
显然,这个2m2m2m次方可以做了DFTDFTDFT后再次方计算。
由于我们取模的是xn−1x^n-1xn1,显然DFTDFTDFT也就是通过单位根计算的。
可以发现,1+xi1+x^i1+xiDFTDFTDFT后对xkx^kxk的贡献为1+wnik1+w^{ik}_n1+wnik
那我们上面的式子DFTDFTDFT后,可以得到fi^=∏k=0n−1(1+wnik)\widehat{f_i}=\prod_{k=0}^{n-1}(1+w^{ik}_n)fi=k=0n1(1+wnik)
怎么算出这东西的真实值呢?考虑xn−1=∏(x−wni)x^n-1=\prod(x-w_{n}^i)xn1=(xwni)
带入x=−1x=-1x=1,可以得到fi^=2[2∣n(n,i)](n,i)\widehat{f_i}=2[2|\frac{n}{(n,i)}]^{(n,i)}fi=2[2∣(n,i)n](n,i)
再对这东西乘上2m2m2m次方,也就是我们答案的F^\widehat{F}F
答案再IDFTIDFTIDFT回去可以得到Ans=1n∑i=0n−1fi^Ans=\frac{1}{n}\sum_{i=0}^{n-1} \widehat{f_i}Ans=n1i=0n1fi
显然,这东西只与(n,i)(n,i)(n,i)的大小有关,也就是说,我们只需要对于nnn的所有因数ddd,算出它的fd^\widehat{f_d}fd以及ϕ(nd)\phi(\frac{n}{d})ϕ(dn)表示它的出现次数,就刻意知道它的答案了。
nnn的因数肯定不会太多,但是要怎么求出这些因数呢?
一种方法是将nnn质因数分解后暴力计算。
由于这里的nnn比较大,我们需要利用Pollard_rho\text{Pollard\_rho}Pollard_rho算法快速分解。

时间复杂度O(n14+d(n)log⁡m)O\left(n^{\frac{1}{4}}+d(n)\log m\right)O(n41+d(n)logm),其中d(n)d(n)d(n)表示nnn的因子个数。

源码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef __int128 Li;
typedef pair<int,int> pii;
#define MAXN 1000005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
const int INF=0x3f3f3f3f;
const int mo=998244353;
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
LL qkpowll(LL a,LL s,LL p){LL t=1;while(s){if(s&1)t=(Li)a*t%p;a=(Li)a*a%p;s>>=1;}return t;}
mt19937 e(time(0));
bool MillerRabin(const LL &x){
    if(x==2||x==3||x==5||x==7||x==11||x==13||x==17)return 1;
    if(!(x%2)||!(x%3)||!(x%5)||!(x%7)||!(x%11)||!(x%13)||!(x%17))return 0;
    LL k=0,r=x-1;uniform_int_distribution<LL> gx(2,x-2);
    while(!(r&1))r>>=1,k++;
    for(int i=0;i<30;i++){
        bool flag=0;LL a=qkpowll(gx(e),r,x);
        if(a==1||a==x-1)continue;
        for(int j=1;j<=k;j++){
            a=(Li)a*a%x;
            if(a==x-1){flag=1;break;}
            if(a==1)return 0;
        }
        if(!flag)return 0;
    }
    return 1;
}
LL PollardRho(const LL &n){
    uniform_int_distribution<LL> gx(1,n-1);
    while(1){
        int times=0;LL d=1,x,y,C;x=y=gx(e);C=gx(e);
        for(int stp=1;;stp<<=1,x=y){
            bool cir=0;
            for(int i=1;i<stp;i++){
                y=((Li)y*y+C)%n;d=(Li)d*Fabs(x-y)%n;times++;
                if(x==y||d==0){cir=1;break;}
                if(times==127){d=gcd(d,n);if(d>1)return d;times=0;}
            }
            if(cir)break;d=gcd(d,n);if(d>1)return d;times=0;
        }
    }
    return -1;
}
LL sta[55],g[55][65];int stak,ct[55],ans;
void work(LL n){
    if(n==1)return ;if(MillerRabin(n)){sta[++stak]=n;return ;}
    LL d=PollardRho(n);while(n%d==0)n/=d;work(d);work(n);
}
int T;LL n,m;
void dfs(int id,LL sum,LL phi){
    if(id==stak+1){
        int t=2*((n/sum)%2);
        t=qkpow(qkpow(t,sum%(mo-1),mo),(m+m)%(mo-1),mo);
        Add(ans,1ll*phi%mo*t%mo,mo);
        return ;
    }
    for(int i=0;i<=ct[id];i++){
        dfs(id+1,sum,phi*g[id][ct[id]-i]);
        if(i<ct[id])sum*=sta[id];
    }
}
int main(){
    //freopen("ntt.in","r",stdin);
    //freopen("ntt.out","w",stdout);
    read(T);
    while(T--){
        read(n);read(m);work(n);LL now=n;
        for(int i=1;i<=stak;i++){
            g[i][0]=1;g[i][1]=sta[i]-1;ct[i]=0;
            while(now%sta[i]==0)now/=sta[i],ct[i]++;
            for(int j=2;j<=ct[i];j++)
                g[i][j]=g[i][j-1]*sta[i];
        }
        dfs(1,1,1);ans=1ll*qkpow(n%mo,mo-2,mo)*ans%mo;
        printf("%d\n",ans);ans=stak=0;
    }
    return 0;
}

谢谢!!!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值