【暖*墟】#洛谷网课1.31# 多项式与概率

多项式及相关操作

一个 R 上的关于 x 的多项式可以写作:

其中 ai ∈ R。x 被称为这个多项式的自由元。

多项式的次数被定义为其最高次项的次数,记为 deg A(x)。

 

 

多项式加法与乘法

 

卷积的概念

 

多项式与点值

 

  • 如何让在多项式系数和点值表达之间转换?-->考虑一组特殊的点值

       

 

复数的加法和乘法

 

struct complex{
     double x,y;
     complex(){}
     complex(double x,double y){this->x=x,this->y=y;}
     complex friend operator +(complex n1,complex n2){return complex(n1.x+n2.x,n1.y+n2.y);}
     complex friend operator -(complex n1,complex n2){return complex(n1.x-n2.x,n1.y-n2.y);}
     complex friend operator *(complex n1,complex n2){return complex(n1.x*n2.x-n1.y*n2.y,n1.x*n2.y+n1.y*n2.x);}
};

 

共轭复数与复数除法

共轭复数:z=a+b*i,z_=a-b*i;

z*z_=a^2+b^2; 则:z1/z2=(z1*z2_)/(z2*z2_)=(z1*z2_)/(c^2+d^2);

 

单位根与本原单位根

 

由欧拉公式: ,可以推出:

在复数域上,本原单位根:(其中 exp(x)=e^x)

在有限域上,本原单位根和数论中的原根有关。

 

离散傅里叶变换(DFT)

  • DFT本质上就是函数对应的点值。

 

单位根的一些性质

两条性质的证明过程:

(1)

(2)

 

“蝴蝶操作”的过程

 

 

拆开式子,再分析一遍:

在枚举第一个式子的时候,我们可以O(1)的得到第二个式子的值,

又因为第一个式子的k在取遍[0,n/2−1]时,k+n/2​取遍了[n/2,n−1]。

那么每次都可以把问题缩小一半,用分治思想不停递归求解即可。

 

快速傅里叶变换(FFT)

位逆序置换 与 非递归FFT

原数列数字01234567
二进制 000 001 010 011 100 101 110 111

 

 

最底层数列数字04261537
二进制 000 100 010 110 001 101 011 111

 

 

 

发现二进制表示反过来了。于是我们可以得到一个转换方法:

for(int i=0;i<len;i++) turn[i]=turn[i>>1]>>1|((i&1)<<L);

 

离散傅里叶变换的逆变换(IDFT)

调整求和顺序,转化成不同的式子。(k是一个独立于 i、j 的值)

 

 

 

FFT进行多项式乘法

假设 A(x), B (x) 是两个不超过 n 次的多项式,

那么他们的乘积 A(x)*B(x) 则可能是不超过 2n − 1 次的多项式。

因此我们一般会对 A(x), B (x) 进行长度至少 2n 的 DFT,

然后把对应的点值乘起来,再进行对应长度的 IDFT。

DFT 与 FFT 都是在 复数域 C 中进行的过程。

但往往是对整数进行操作,并且经常要对某个素数 p 取模。

考虑在模素数的时候,是否存在和单位根性质类似的元素。

实现思路:系数表示法—>点值表示法—>系数表示法。

 

原根的定义与性质

设 p 是素数。由费马小定理,对于任意 a 满足互质,有:

a^(p−1) ≡ 1 (mod p)

g 称为模 p 的原根,当且仅当 g0, g1, . . . , g(p−2)在模 p 意义下互不相同。

可以证明,原根总是存在的。原根的性质和本原单位根非常类似。

换句话说,在 mod p 意义下,g 可以被看做一个 p − 1 次本原单位根。

 

FFT具体过程及代码实现

三重循环:1.合并的序列长度 ; 2.枚举具体每一位;3.蝴蝶操作优化。

void FFT(complex *a,int typ){
    for(int i=0;i<len;i++)
        if(i<turn[i]) swap(a[i],a[turn[i]]);
    for(int l=1;l<len;l<<=1){
        wn=complex(cos(pi/l),typ*sin(pi/l));
        for(int p=0;p<len;p+=(l<<1)){
            w=complex(1,0); //a+b*i
            for(int i=p;i<p+l;i++,w=w*wn){
                tmpx=a[i],tmpy=w*a[i+l];
                a[i]=tmpx+tmpy,a[i+l]=tmpx-tmpy;
            } //↑↑用“蝴蝶操作”优化
        }
    }
}

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

//【p3803】FFT求卷积模板

void reads(int &x){ //读入优化(正负整数)
    int fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const int N=(1<<21)+10;

struct complex{ //复数
    double x,y;
    complex(){} //复数的相关运算
    complex(double x,double y){this->x=x,this->y=y;}
    complex friend operator +(complex n1,complex n2)
      {return complex(n1.x+n2.x,n1.y+n2.y);}
    complex friend operator -(complex n1,complex n2)
      {return complex(n1.x-n2.x,n1.y-n2.y);}
    complex friend operator *(complex n1,complex n2)
      {return complex(n1.x*n2.x-n1.y*n2.y,n1.x*n2.y+n1.y*n2.x);}
}a[N],b[N],tmpx,tmpy,wn,w;

const double pi=3.1415926535897632;

int n,m,turn[N],len=1,L=-1;

void FFT(complex *a,int typ){
    for(int i=0;i<len;i++)
        if(i<turn[i]) swap(a[i],a[turn[i]]);
    for(int l=1;l<len;l<<=1){
        wn=complex(cos(pi/l),typ*sin(pi/l));
        for(int p=0;p<len;p+=(l<<1)){
            w=complex(1,0); //a+b*i
            for(int i=p;i<p+l;i++,w=w*wn){
                tmpx=a[i],tmpy=w*a[i+l];
                a[i]=tmpx+tmpy,a[i+l]=tmpx-tmpy;
            } //↑↑用“蝴蝶操作”优化
        }
    }
}

int main(){ 

    reads(n),reads(m); //↓↓注意从0次开始
    for(int i=0;i<=n;i++) scanf("%lf",&a[i].x);
    for(int i=0;i<=m;i++) scanf("%lf",&b[i].x);
    
    while(len<=(n+m)) len<<=1,L++;
    
    for(int i=0;i<=len;i++) turn[i]=(turn[i>>1]>>1)|((i&1)<<L);
    //↑↑位逆序替换,就找到了对应的turn位置
     
    /*  实现思路:系数表示法—>点值表示法—>系数表示法。
        后面的1表示要进行的变换是什么类型。
        1表示从系数变为点值,-1表示从点值变为系数。 */

    FFT(a,1),FFT(b,1); //从系数变为点值
    for(int i=0;i<=len;i++) a[i]=a[i]*b[i];

    FFT(a,-1); for(int i=0;i<=n+m;i++) 
        printf("%d ",(int)(a[i].x/len+0.5)); //四舍五入
}
洛谷p3803-模板

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

//【p1919】FFT求大整数乘法

void reads(int &x){ //读入优化(正负整数)
    int fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const int N=1000019;

struct complex{ //复数
    double x,y;
    complex(){} //复数的相关运算
    complex(double x,double y){this->x=x,this->y=y;}
    complex friend operator +(complex n1,complex n2)
      {return complex(n1.x+n2.x,n1.y+n2.y);}
    complex friend operator -(complex n1,complex n2)
      {return complex(n1.x-n2.x,n1.y-n2.y);}
    complex friend operator *(complex n1,complex n2)
      {return complex(n1.x*n2.x-n1.y*n2.y,n1.x*n2.y+n1.y*n2.x);}
}a[N],b[N],tmpx,tmpy,wn,w;

const double pi=3.1415926535897632;

int n,m,turn[N],len=1,L=-1;

char s1[N],s2[N]; int aa=0,bb=0,ans[N];

void FFT(complex *a,int typ){
    for(int i=0;i<len;i++)
        if(i<turn[i]) swap(a[i],a[turn[i]]);
    for(int l=1;l<len;l<<=1){
        wn=complex(cos(pi/l),typ*sin(pi/l));
        for(int p=0;p<len;p+=(l<<1)){
            w=complex(1,0); //a+b*i
            for(int i=p;i<p+l;i++,w=w*wn){
                tmpx=a[i],tmpy=w*a[i+l];
                a[i]=tmpx+tmpy,a[i+l]=tmpx-tmpy;
            } //↑↑用“蝴蝶操作”优化
        }
    }
}

int main(){ //把每一位看成一个系数,最后再整合

    reads(n); scanf("%s%s",s1,s2);
    for(int i=n-1;i>=0;i--) a[aa++].x=s1[i]-48;
    for(int i=n-1;i>=0;i--) b[bb++].x=s2[i]-48;
    
    while(len<(n+n)) len<<=1,L++;
    
    for(int i=0;i<=len;i++) turn[i]=(turn[i>>1]>>1)|((i&1)<<L);
    //↑↑位逆序替换,就找到了对应的turn位置
     
    /*  实现思路:系数表示法—>点值表示法—>系数表示法。
        后面的1表示要进行的变换是什么类型。
        1表示从系数变为点值,-1表示从点值变为系数。 */

    FFT(a,1),FFT(b,1); //从系数变为点值
    for(int i=0;i<=len;i++) a[i]=a[i]*b[i]; //记录乘积答案

    FFT(a,-1); //把乘积答案转化为各位置的系数
    for(int i=0;i<=len;i++){
        ans[i]+=(int)(a[i].x/len+0.5); //系数整合为大整数 
        if(ans[i]>=10) ans[i+1]+=ans[i]/10,ans[i]%=10,
            len+=(i==len); //判断是否要多一位
    } while(!ans[len]&&len>=1) len--; //删除前导零

    len++; while(--len>=0) cout<<ans[len]; //输出答案
}
洛谷p1919-大整数乘法

 

 

数论变换

根据原根和费马小定理的规律,可以推出:

如果 n = 2^k,则也可以利用与 FFT 类似的方式快速的计算数论变换。

快速数论变换对所选取的素数模数有着特殊的要求,即满足:2^k = n | p − 1。

比如常见的模数:

p(UOJ)= 998244353 = 7 · 17 · 2^23 + 1

就是一个可以用于快速数论变换的模数。

 

FFT 可以用来计算多项式乘法。卷积可以写成多项式乘法,因此 FFT 可以计算序列的卷积。

 

【例题】洛谷p3338 力

【解题思路】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

//【p3338】力

void reads(int &x){ //读入优化(正负整数)
    int fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const int N=(1<<18)+10;

struct complex{ //复数
    double x,y;
    complex(){} //复数的相关运算
    complex(double x,double y){this->x=x,this->y=y;}
    complex friend operator +(complex n1,complex n2)
      {return complex(n1.x+n2.x,n1.y+n2.y);}
    complex friend operator -(complex n1,complex n2)
      {return complex(n1.x-n2.x,n1.y-n2.y);}
    complex friend operator *(complex n1,complex n2)
      {return complex(n1.x*n2.x-n1.y*n2.y,n1.x*n2.y+n1.y*n2.x);}
}a[N],b[N],tmpx,tmpy,wn,w;

const double pi=3.1415926535897632;

int n,turn[N],len=1,L=-1;

double out[N],q[N];

void FFT(complex *a,int typ){
    for(int i=0;i<len;i++)
        if(i<turn[i]) swap(a[i],a[turn[i]]);
    for(int l=1;l<len;l<<=1){
        wn=complex(cos(pi/l),typ*sin(pi/l));
        for(int p=0;p<len;p+=(l<<1)){
            w=complex(1,0); //a+b*i
            for(int i=p;i<p+l;i++,w=w*wn){
                tmpx=a[i],tmpy=w*a[i+l];
                a[i]=tmpx+tmpy,a[i+l]=tmpx-tmpy;
            } //↑↑用“蝴蝶操作”优化
        }
    }
}

int main(){ 

    reads(n); for(int i=1;i<=n;i++) scanf("%lf",&q[i]);
    
    for(int i=1;i<=n;i++) a[i].x=q[i],b[i].x=1.0/i/i;
    //把 b[i].x=1.0/i/i 换成 1.0/(i*i) 会被卡精度↑↑
    
    while(len<=((n+1)<<1)) len<<=1,L++;
    
    for(int i=0;i<len;i++) turn[i]=turn[i>>1]>>1|(i&1)<<L;
    //↑↑位逆序替换,就找到了对应的turn位置
     
    /*  实现思路:系数表示法—>点值表示法—>系数表示法。
        后面的1表示要进行的变换是什么类型。
        1表示从系数变为点值,-1表示从点值变为系数。 */

    FFT(a,1),FFT(b,1); //从系数变为点值
    for(int i=0;i<len;i++) a[i]=a[i]*b[i];

    FFT(a,-1); //从点值变为系数
    for(int i=1;i<=n;i++) out[i]+=a[i].x/len;
    for(int i=0;i<len;i++) a[i]=complex(0,0);
    for(int i=1;i<=n;i++) a[n+1-i]=complex(q[i],0);

    FFT(a,1); //从系数变为点值
    for(int i=0;i<len;i++) a[i]=a[i]*b[i];

    FFT(a,-1); //从点值变为系数
    for(int i=1;i<=n;i++) out[n+1-i]-=a[i].x/len;
    
    for(int i=1;i<=n;i++) printf("%.3lf\n",out[i]);
}

 

矩阵的各种运算

 

矩阵的转置

 

矩阵的运算

两个矩阵的和或差定义为对应元素求和或差。

用一个数乘矩阵定义为用其乘以矩阵中的每个数。

 

矩阵乘法

 

线性递推数列

 

 

邻接矩阵

简单图 G 的邻接矩阵 A = (aij) 是一个 | V | 阶的方阵,

其中若顶点 i 到顶点 j 有边则 aij = 1,反之 aij = 0。

图的邻接矩阵在一些图上的计数问题中有应用。

若 G 不是简单图,可以令 aij 表示顶点 i 到顶点 j 的边的数量。

 

图上路径计数

给一个有向图 G(可能有重边和自环),对于所有点对 (u, v),

计算 u 到 v 的长度为 k 的路径有多少。

记所求答案为 f (k, u, v),并令 a uv 表示顶点 u 到顶点 v 的边的数量,

则有:可以发现这是矩阵的形式。

 

线性方程组

 

行初等变换

一个矩阵的行初等变换指的是对一个矩阵施行的下列变换:

1. 交换矩阵的两行;

2. 用一个非零的数乘矩阵的某一行;

3. 用一个数乘以矩阵的某一行后加到另一行。

对方程组的增广矩阵作行初等变换不改变对应方程组的解。

我们希望通过行初等变换将矩阵化为便于求解的形式。

一个思路就是将矩阵化为阶梯型矩阵。这个过程就是 高斯消元

  • 我们从左到右考虑系数矩阵的每一列。
  • 对于第 i 列,找到一行使第 i 个元素非 0,将此行与第 i 行交换,aii != 0。
  • 然后我们对于每个 j 满足 j > i,将第 i 行乘以 −aji/aii 加到第 j 行上。
  • 经过这样的操作,对于 j > i,有 aji = 0。
  • 依次考虑 1 ≤ i ≤ n 就完成了高斯消元的过程。时间复杂度为O(n^3)。

可以发现,高斯消元之后,第 n 个方程已经给出了第 n 个未知数的值。

将第 n 个未知数的值代入第 n − 1 个方程,就得到了第 n − 1 个未知数的值。

......反复如此做,就得到了所有未知数的值。

Q:如果在某一步的时候找不到对应的 aii! = 0 怎么办?

A:这说明方程组没有唯一解,有可能是无解或者无穷多组解。

 

异或方程组

考虑取值为 0 或 1 的变量 x1, . . . , xn,给定 n 个条件,

每个条件选出一些变量并给出他们的异或值。

这其实就是 mod 2 意义下的线性方程组,也可以用高斯消元来解。

注意消元的过程其实就是将一个方程异或到另一个方程上,可用 bitset优化。

 

概率初步

 

随机变量与期望

 

期望的线性性质

 

图上的概率及期望问题

 

 

                       ——时间划过风的轨迹,那个少年,还在等你。

 

posted @ 2019-01-31 09:09  花神&缘浅flora  阅读(558)  评论(0编辑  收藏  举报