[51nod1348]乘积之和 NTT+分治

题意

给出由N个正整数组成的数组A,有Q次查询,每个查询包含一个整数K,从数组A中任选K个(K <= N)把他们乘在一起得到一个乘积。求所有不同的方案得到的乘积之和,由于结果巨大,输出Mod 100003的结果即可。例如:1 2 3,从中任选1个共3种方法,{1} {2} {3},和为6。从中任选2个共3种方法,{1 2} {1 3} {2 3},和为2 + 3 + 6 = 11。

Input

第一行:包括2个数N,Q,中间用空格分隔。(1 <= N, Q <= 50000)
第2 至 N + 1行:每行1个数A[i],对应数组A的元素。(1 <= A[i] <= 10^9)
第N + 2 至 N + Q + 1行:每行1个数K。(1 <= K <= N)

Output

输出共Q行,每行1个数,对应每个查询的结果。

题解

首先考虑这个问题可以 dp ,设 f[i] 为选了 i 个数时答案是多少。
方程为f[i]=f[j]f[k]
可以发现这是个卷积的形式,但是直接 dp ,没法降低复杂度。
考虑分治,对于一个区间 (l,r) ,求出对于 (l,mid) 的答案和对于 (mid+1,r) 的答案,这样区间 (l,r) 的答案就可以通过两个子区间求出,使用上面的 dp 方程解决,由于这是个卷积,就可以用NTT实现,找两个常用的模数,再用 CRT 合起来,中间注意不要爆 longlong
总复杂度 O(nlg2n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll GMOD=100003;
ll read(){
    ll rt=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){rt=rt*10+ch-'0';ch=getchar();}
    return rt;
}
void write(ll num){
    if(!num)puts("0");
    static char str[50];
    static int _tmp=0;
    while(num){str[++_tmp]=num%10+'0';num/=10;}
    while(_tmp){putchar(str[_tmp--]);}
}
ll n,q,data[300000],f[25][300000],sa1[300000],sa2[300000],sb1[300000],sb2[300000];
int R[300000];
ll ksm(ll x,ll y,ll z){
    ll ret=1;
    while(y){
        if(y&1){ret=(x*ret)%z;}
        x=(x*x)%z;
        y>>=1;
    }
    return ret;
}
ll msc(ll x,ll y,ll z){
    ll ret=0;
    while(y){
        if(y&1){ret=(ret+x)%z;}
        x=(x+x)%z;
        y>>=1;
    }
    return ret;
}
void Init(){
    n=read();q=read();
    for(int i=1;i<=n;i++)data[i]=read();
}
void Solve(){
    while(q--){
        write(f[0][read()]);
        putchar('\n');
    }
}
void NTT(ll *x,ll len,int flag,ll MOD){
    for(int i=0;i<len;i++)if(i<R[i])swap(x[i],x[R[i]]);
    for(int i=1;i<len;i<<=1){
        ll wn=ksm(3,(MOD-1)/((ll)i*2LL),MOD);
        for(int j=0;j<len;j+=(i<<1)){
            ll w=1;
            for(int k=0;k<i;k++,w=(w*wn)%MOD){
                ll a=x[j+k],b=(w*x[j+k+i])%MOD;
                x[j+k]=(a+b)%MOD;x[j+k+i]=(a-b+MOD)%MOD;
            }
        }
    }
    if(flag==-1){
        reverse(x+1,x+len);
        ll inv=ksm(len,MOD-2,MOD);
        for(int i=0;i<len;i++){
            x[i]=(x[i]*inv)%MOD;
        }
    }
}
void DAC(int l,int r,int depth){
    for(int i=0;i<=r-l+1;i++)f[depth][i]=0;
    if(l==r){
        f[depth][0]=1;
        f[depth][1]=data[l]%GMOD;
        return;
    }
    int mid=(l+r)>>1;
    DAC(l,mid,depth+1);
    for(int i=0;i<=r-l+1;i++)f[depth][i]=f[depth+1][i];
    DAC(mid+1,r,depth+1);
    int m=r-l+1,L=0,tmp=0;
    for(tmp=1;tmp<=m;tmp<<=1)L++;
    for(int i=0;i<tmp;i++)R[i]=0;
    for(int i=0;i<tmp;i++)R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    for(int i=r-(mid)+1;i<tmp;i++)f[depth+1][i]=0;
    for(int i=mid+1-l+1;i<tmp;i++)f[depth][i]=0;
    for(int i=0;i<tmp;i++){
        sa1[i]=sa2[i]=f[depth][i];
        sb1[i]=sb2[i]=f[depth+1][i];
    }
    NTT(sa1,tmp,1,998244353LL);NTT(sb1,tmp,1,998244353LL);
    for(int i=0;i<tmp;i++)sa1[i]=(sa1[i]*sb1[i])%998244353LL;
    NTT(sa1,tmp,-1,998244353LL);
    NTT(sa2,tmp,1,1004535809LL);NTT(sb2,tmp,1,1004535809LL);
    for(int i=0;i<tmp;i++)sa2[i]=(sa2[i]*sb2[i])%1004535809LL;
    NTT(sa2,tmp,-1,1004535809LL);
    ll inv1=ksm(1004535809LL,998244353LL-2LL,998244353LL);
    ll inv2=ksm(998244353LL,1004535809LL-2LL,1004535809LL);
    ll M=998244353LL*1004535809LL;
    for(int i=0;i<=m;i++){
        f[depth][i]=(sa1[i]*1004535809LL)%M;
        f[depth][i]=msc(f[depth][i],inv1,M);
        ll ttt=(sa2[i]*998244353LL)%M;
        ttt=msc(ttt,inv2,M);
        f[depth][i]=(f[depth][i]+ttt)%M;
        f[depth][i]+=GMOD;
        f[depth][i]%=GMOD;
    }
}
int main(){
    Init();
    DAC(1,n,0);
    Solve();
    return 0;
}

其实这道题不需要 NTT , FFT 就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值