• 【笔记】 卷积


    题目

    • HDU 4609 3-idiots
      题目链接
      题解
      这个题考察了如何转化成多项式乘法,然后去重和计数很有意思
    • HDU 1402 A*B problem plus
      题目链接
      将整数转化成向量,最后得到的卷积后的向量处理一下每一位的进位就是结果
    • BZOJ 2194 快速傅立叶之二
      题目链接
      FFT 能解决形如 c[k] =sigma(a[p]*b[k-p]) (0<=p<=k) 的式子,如果是c[k] = sigma(a[p],b[p+k]),可以将其中一个向量倒过来,这样就变成第一种情况了,当然所求的下标也变化了
    • BZOJ 3527 力
      题目链接
      公式推导
      和BZOJ2194类似,我们要将原式转化成c[k] =sigma(a[p]*b[k-p]) (0<=p<=k) , 熟练运用换元法、缩放和向量转置,得到满足卷积条件的表达式
    • BZOJ 3771 Triple
      题目链接
      容斥分析
      这道题考点在于是否了解DFT、IDFT之后求的是什么东西,只要心里清楚,就很容易做了

    什么是卷积

    参考资料1
    参考资料2

    多项式乘法就是求卷积
    1 3 3 4
    把这个数组转化成num数组,num[i]表示数字i的有num[i]个。
    num = {0 1 0 2 1}
    代表0的有0个,1有1个,2有0个,3有2个,4有1个。
    使用FFT解决的问题就是num数组和num数组卷积。
    num数组和num数组卷积的解决,其实就是从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数,他们的和每个值各有多少个
    例如:
    {0 1 0 2 1}{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }
    长度为n的数组和长度为m的数组卷积,结果是长度为n+m-1的数组。
    {0 1 0 2 1}
    {0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }。
    这个结果的意义如下:
    从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数
    取两个数和为 2 的取法是一种:1+1
    和为 4 的取法有四种:1+3, 1+3 ,3+1 ,3+1
    和为 5 的取法有两种:1+4 ,4+1;
    和为 6的取法有四种:3+3,3+3,3+3,3+3,3+3
    和为 7 的取法有四种: 3+4,3+4,4+3,4+3
    和为 8 的取法有 一种:4+4

    关于多项式

    • 次数界:对于多项式A(x),系数为ai,设最高非零系数为ak,任何大于k的整数都是A(x)的次数界
    • 系数表达式:
    • 系数向量:a=(a0,a1,...,an−1)
    • 点值表达方式:{(x0,y0),(x1,y1),...,(xn−1,yn−1)} ,其中xk各不相同,yk=A(xk)

    快速傅立叶变换(FFT)

    快速计算DFT(离散傅里叶变换)的算法
    时间复杂度:O(nlogn)

    复数

    const double pi = acos(-1);
    const double pi_2 = 2*pi;
    struct Complex // 复数
    {
        double r, i;
        Complex(double _r = 0, double _i = 0) :r(_r), i(_i) {}
        Complex operator +(const Complex &b) {
            return Complex(r + b.r, i + b.i);
        }
        Complex operator -(const Complex &b) {
            return Complex(r - b.r, i - b.i);
        }
        Complex operator *(const Complex &b) {
            return Complex(r*b.r - i*b.i, r*b.i + i*b.r);
        }
    };
    

    FFT模板

    • 非递归
    void func1(Complex pos[],int n){
        int j = __builtin_ctz(n)-1;
        for(int i=0;i<n;i++) pos[i] = pos[i>>1]>>1 | ((i&1)<<j);
    }
    void fft(Complex y[],int len,int on){
        change(y,len);
        for(int h=2;h<=len;h<<=1){
            Complex wn(cos(-on*pi_2/h),sin(-on*pi_2/h));
            for(int j=0;j<len;j+=h){
                Complex w(1,0);
                for(int k=j;k<j+h/2;++k){
                    Complex u=y[k],t=w*y[k+h/2];
                    y[k]=u+t;
                    y[k+h/2]=u-t;
                    w=w*wn;
                }
            }
        }
        if(on==-1)
        for(int i=0;i<len;++i)
        y[i].r/=len;
    }
    
    

    步骤

    • 补0
      在两个多项式前面补0,得到两个2n次多项式,设系数向量分别为v1和v2。
    • 求值
      使用FFT计算f1=DFT(v1)和f2=DFT(v2)。则f1与f2为两个多项式在2n次单位根处的取值(即点值表示)。
    • 乘法
      把f1与f2每一维对应相乘,得到f,代表对应输入多项式乘积的点值表示。
    • 插值
      使用IFFT计算v=IDFT(f),其中v就是乘积的系数向量。
    while(len < len1*2) len<<=1;
    for(int i = 0;i<len1;i++)
    	x1[i] = Complex(num[i],0);
    for(int i = len1;i<len;i++)
    	x1[i] = Complex(0,0);
    fft(x1,len,1);
    for(int i = 0;i<len;i++)
    	x1[i]= x1[i]*x1[i];
    fft(x1,len,-1);
    for(int i = 0;i<len;i++)
    	num[i] = (long long)(x1[i].r+0.5);
    
    

    快速数论变换(NTT)

    当FFT需要取模P且P存在原根时,就能用NTT来求卷积
    原根的知识见离散对数
    NTT用到的部分素数及原根
    如果不在表中可以自行计算

    const int N = 1000;
    bool not_prime[N+5];
    int prime[N+5],tot_prime,pr[N+5],tot_pr;
    int g;
    void sss(){ 
        for(int i=2;i<N;i++){
        if(!not_prime[i]) prime[++tot_prime]=i;
        for(int j=1;j<=tot_prime;j++){
            int t = i*prime[j];if(t>=N) break;not_prime[t]=true;
            if(i%prime[j]==0)break;
        }
        }
    }
    void get(int p){    int temp=p-1;
        for(int i=1;i<=tot_prime&&prime[i]*prime[i]<=temp;i++)
        if(temp%prime[i]==0){
            while(temp%prime[i]==0) temp /= prime[i];
            pr[++tot_pr] = prime[i];
        }
        if(temp>1) pr[++tot_pr]=temp;
    }
    bool check(int d,int p){ 
        if (__gcd(d,p)!=1)return 0;
        for(int i=1;i<=tot_pr;i++)if(qpow(d,(p-1)/pr[i])==1)return 0;
        return 1; 
    }
    int get_g(int p) {
        int g=1;
        sss();get(p);
        for(int i=2;i<p;i++) if(check(i,p)) {g=i;break;}
        return g;
    }
    
    

    ntt的接口使用和fft 类似,长度一定是2的幂次倍

    int rev(int x,int r) {
        int ans=0;
        for(int i=0;i<r;i++) if(x&(1<<i)) ans+=1<<(r-i-1);
        return ans;
    }
    void ntt(int n,ll a[],int on) {
        int r = 0;
        while((1<<r)<n) r++;
        for(int i=0;i<n;i++) {
            int tmp = rev(i,r);
            if(i < tmp)  swap(a[i],a[tmp]);
        }
        for(int s=1;s<=r;s++) {
            int m = 1<<s;
            ll wn=qpow(g,(mod-1)/m);
            for(int k=0;k<n;k+=m){
                ll w=1;
                for(int j=0;j<(m>>1);j++) {
                    ll t = w * a[k+j+(m>>1)] % mod;
                    ll u=a[k+j];
                    a[k+j] = (u+t)%mod;
                    a[k+j+(m>>1)] = (u-t)%mod;if(a[k+j+(m>>1)]<0)a[k+j+(m>>1)]+=mod;
                    w = w*wn%mod;
                }
            }
        }
        if(on==-1) {
            for(int i=1;i<(n>>1);i++) swap(a[i],a[n-i]);
            ll inv = qpow(n,mod-2);
            for(int i=0;i<n;i++) a[i] = a[i] * inv % mod;
        }
    }
    
  • 相关阅读:
    python基础-元组类型
    python基础-列表类型
    python基础-字符串类型
    python基础-数字类型
    python基础-循环
    python基础-短路和拷贝
    python基础-人机交互和运算符
    python基础-垃圾回收机制
    python基础-python介绍
    UCF Local Programming Contest 2017(2020-4-6)
  • 原文地址:https://www.cnblogs.com/greenty1208/p/9127929.html
Copyright © 2020-2023  润新知