zoukankan      html  css  js  c++  java
  • 牛客挑战赛14-F细胞

    https://www.nowcoder.com/acm/contest/81/F

    循环卷积的裸题,太久没做FFT了,这么裸的循环卷积都看不出来

    注意一下本文的mod 都是指表示幂的模数,而不是NTT用到的模数

    • 首先我们先不管m,考虑多项式

    可以发现这个是一个多项式的n次幂,正常求一个多项式的n次幂,可以用快速幂套NTT,复杂度n*log(n)*log(n), 最多只能做n在1e4左右的题。

    • 现在在来考虑m,则原式为。

          显然这就是循环卷积的常见形式

        如果先用快速幂套NTT 把多项式系数算出来a[i], 再对i%mod同余的系数进行累加,时间和空间都是会爆炸的。

      不过在多项式快速幂实现的时候不难发现,可以每做一次多项式乘法,就对幂取余一次,合并幂的余数相同的项。这样空间可以降到2*mod, 时间复杂度    mod*log(mod)*log(n)      但是这样还是会超时的

    • 最后重点来了,上面是一般的情况的下循环卷积的做法,循环卷积还有一种特殊情况,就是指数的mod=2^m 时,这时循环卷积可以直接变成频域上的2^m-1 次多项式的点乘(注意要系数等于2^m-1 的点乘, 不需要先以前一样开两倍大小,以防止多项式系数溢出,这里就是要溢出才能保证正确性),这时可以发现,NTT 前可NTT后都是mod-1次多项,没有系数合并的那一步,所以干脆中间乘的时候,就不要NTT回来,  直接在点成的时候做快速幂。 这样只需做一次NTT和逆NTT.时间复杂度  mod*(log(n)+log(mod))
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    #define N 2000005
    ll a[N],b[N];
    const ll PMOD=998244353;
    const ll PR=3;
    static ll qp[30];
    ll res[N];
    struct NTT__container
    {
        NTT__container()
        {
            int  t,i;
            for( i=0; i<21; i++)///注意循环上界与2n次幂上界相同
            {
                t=1<<i;
                qp[i]=quick_pow(PR,(PMOD-1)/t);
            }
        }
        ll quick_pow(ll x,ll n)
        {
            ll ans=1;
            while(n)
            {
                if(n&1)
                    ans=ans*x%PMOD;
                x=x*x%PMOD;
                n>>=1;
            }
            return ans;
        }
        int get_len(int n)///计算刚好比n大的2的N次幂
        {
            int i,len;
            for(i=(1<<30); i; i>>=1)
            {
                if(n&i)
                {
                    len=(i<<2);
                    break;
                }
            }
            return len;
        }
        inline void NTT(ll F[],int len,int type)
        {
            int id=0,h,j,k,t,i;
            ll E,u,v;
            for(i=0,t=0; i<len; i++)///逆位置换
            {
                if(i>t)    swap(F[i],F[t]);
                for(j=(len>>1); (t^=j)<j; j>>=1);
            }
            for( h=2; h<=len; h<<=1)///层数
            {
                id++;
                for( j=0; j<len; j+=h)///遍历这层上的结点
                {
                    E=1;///旋转因子
                    for(int k=j; k<j+h/2; k++)///遍历结点上的前半序列
                    {
                        u=F[k];///A[0]
                        v=(E*F[k+h/2])%PMOD;///w*A[1]
                        ///对偶计算
                        F[k]=(u+v)%PMOD;
                        F[k+h/2]=((u-v)%PMOD+PMOD)%PMOD;
                        ///迭代旋转因子
                        E=(E*qp[id])%PMOD;///qp[id]是2^i等分因子
                    }
                }
            }
            if(type==-1)
            {
                int i;
                ll inv;
                for(i=1; i<len/2; i++)///转置,因为逆变换时大家互乘了对立点的因子
                    swap(F[i],F[len-i]);
                inv=quick_pow(len,PMOD-2);///乘逆元还原
                for( i=0; i<len; i++)
                    F[i]=(F[i]%PMOD*inv)%PMOD;
            }
        }
        inline void inv(ll *a,int len)///答案存在res中
        {
            if(len==1)
            {
                res[0]=quick_pow(a[0],PMOD-2);
                return ;
            }
            inv(a,len>>1);///递归
            static ll temp[N];
            memcpy(temp,a,sizeof(ll)*(len>>1));
            NTT(temp,len,1);
            NTT(res,len,1);
            int i;
            for(i=0; i<len; i++)
                res[i]=res[i]*(2-temp[i]*res[i]%PMOD+PMOD)%PMOD;///多项式逆元迭代公式
            NTT(res,len,-1);
            memset(res+(len>>1),0,sizeof(ll)*(len>>1));
        }
        void mul(ll x[],ll y[],int len)///答案存在x中
        {
            int i;
            NTT(x,len,1);///先变换到点值式
            NTT(y,len,1);///先变换到点值式上
            for(i=0; i<len; i++)
                x[i]=(x[i]*y[i])%PMOD;///在点值上点积
            NTT(x,len,-1);///再逆变换回系数式
        }
    
    } cal;
    void print(ll a[],int len)
    {
        int high=0,i;
        for(i=len-1; i>=0; i--)
        {
            if(a[i])
            {
                high=i;
                break;
            }
        }
        for(i=high; i>=0; i--)putchar(a[i]+'0');
        puts("");
    }
    int main()
    {
        int m,i,j,k,len;
        long long n;
    //    printf("%lld
    ",PMOD);
        scanf("%lld%d",&n,&m);
        len=1<<m;
        a[0]=1;
        a[1]=2;
        cal.NTT(a,len,1);
        for(i=0;i<len;i++)
        {
            a[i]=cal.quick_pow(a[i],n);
        }
        cal.NTT(a,len,-1);
        long long temp=1,ans=0;
        for(i=0;i<len;i++)
        {
            ans+=temp*a[i]%PMOD;
            temp=temp*2222303%PMOD;
        }
        printf("%lld
    ",ans%PMOD);
        return 0;
    }
    View Code
  • 相关阅读:
    使用JDBC连接MySql时出现:The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration
    Mysql Lost connection to MySQL server at ‘reading initial communication packet', system error: 0
    mysql-基本命令
    C# 监听值的变化
    DataGrid样式
    C# 获取当前日期时间
    C# 中生成随机数
    递归和迭代
    PHP 时间转几分几秒
    PHP 根据整数ID,生成唯一字符串
  • 原文地址:https://www.cnblogs.com/qswg/p/9240573.html
Copyright © 2011-2022 走看看