数学(论)里的一些定理(莫比乌斯反演,傅立叶变换,数论变换...)

莫比乌斯反演

莫比乌斯反演在数论中占有重要的地位,许多情况下能大大简化运算。那么我们先来认识莫比乌斯反演公式。

定理:是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论

在上面的公式中有一个函数,它的定义如下:

(1)若,那么

(2)若均为互异素数,那么

(3)其它情况下

对于函数,它有如下的常见性质:

(1)对任意正整数

(2)对任意正整数

线性筛选求莫比乌斯反演函数代码。

void Init()
{
    memset(vis,0,sizeof(vis));
    mu[1] = 1;
    cnt = 0;
    for(int i=2; i<N; i++)
    {
        if(!vis[i])
        {
            prime[cnt++] = i;
            mu[i] = -1;
        }
        for(int j=0; j<cnt&&i*prime[j]<N; j++)
        {
            vis[i*prime[j]] = 1;
            if(i%prime[j]) mu[i*prime[j]] = -mu[i];
            else
            {
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
}

有了上面的知识,现在我们来证明莫比乌斯反演定理。

证明

证明完毕!

嗯,有了莫比乌斯反演,很多问题都可以简化了,接下来我们来看看莫比乌斯反演在数论中如何简化运算的。

题目:http://bz.cdqzoi.com/JudgeOnline/problem.php?id=2818

题意:给一个正整数,其中,求使得为质数的的个数,

分析:对于本题,因为是使得为质数,所以必然要枚举小于等于的质数,那么对于每一个质数,只

需要求在区间中,满足有序对互质的对数。

也就是说,现在问题转化为:在区间中,存在多少个有序对使得互质,这个问题就简单啦,因为

是有序对,不妨设,那么我们如果枚举每一个,小于有多少个互素,这正是欧拉函数。所以

我们可以递推法求欧拉函数,将得到的答案乘以2即可,但是这里乘以2后还有漏计算了的,那么有哪些呢?

且为素数的情况,再加上就行了。

代码:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <bitset>

using namespace std;
typedef long long LL;
const int N = 10000010;

bitset<N> prime;
LL phi[N];
LL f[N];
int p[N];
int k;

void isprime()
{
    k = 0;
    prime.set();
    for(int i=2; i<N; i++)
    {
        if(prime[i])
        {
            p[k++] = i;
            for(int j=i+i; j<N; j+=i)
                prime[j] = false;
        }
    }
}

void Init()
{
    for(int i=1; i<N; i++)  phi[i] = i;
    for(int i=2; i<N; i+=2) phi[i] >>= 1;
    for(int i=3; i<N; i+=2)
    {
        if(phi[i] == i)
        {
            for(int j=i; j<N; j+=i)
                phi[j] = phi[j] - phi[j] / i;
        }
    }
    f[1] = 0;
    for(int i=2;i<N;i++)
        f[i] = f[i-1] + (phi[i]<<1);
}

LL Solve(int n)
{
    LL ans = 0;
    for(int i=0; i<k&&p[i]<=n; i++)
        ans += 1 + f[n/p[i]];
    return ans;
}

int main()
{
    Init();
    isprime();
    int n;
    scanf("%d",&n);
    printf("%I64d\n",Solve(n));
    return 0;
}

上题不算太难,普通的欧拉函数就可以搞定,接下来我们来看看它的升级版。

题意:给定两个数,其中,求为质数的有多少对?其中的范

围是

分析:本题与上题不同的是不一定相同。在这里我们用莫比乌斯反演来解决,文章开头也说了它能大大简化

运算。我们知道莫比乌斯反演的一般描述为:

其实它还有另一种描述,本题也是用到这种。那就是:

好了,到了这里,我们开始进入正题。。。

对于本题,我们设

为满足的对数

为满足的对数

那么,很显然,反演后得到

因为题目要求是为质数,那么我们枚举每一个质数,然后得到

如果直接这样做肯定TLE,那么我们必须优化。

我们设,那么继续得到

到了这里,可以看出如果我们可以先预处理出所有的对应的的值,那么本题就解决了。

我们设,注意这里为素数,

那么,我们枚举每一个,得到,现在分情况讨论:

(1)如果整除,那么得到

(2)如果不整除,那么得到

代码:

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;
typedef long long LL;
const int N = 10000005;

bool vis[N];
int p[N];
int cnt;
int g[N],u[N],sum[N];

void Init()
{
    memset(vis,0,sizeof(vis));
    u[1] = 1;
    cnt = 0;
    for(int i=2;i<N;i++)
    {
        if(!vis[i])
        {
            p[cnt++] = i;
            u[i] = -1;
            g[i] = 1;
        }
        for(int j=0;j<cnt&&i*p[j]<N;j++)
        {
            vis[i*p[j]] = 1;
            if(i%p[j])
            {
                u[i*p[j]] = -u[i];
                g[i*p[j]] = u[i] - g[i];
            }
            else
            {
                u[i*p[j]] = 0;
                g[i*p[j]] = u[i];
                break;
            }
        }
    }
    sum[0] = 0;
    for(int i=1;i<N;i++)
        sum[i] = sum[i-1] + g[i];
}

int main()
{
    Init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        LL n,m;
        cin>>n>>m;
        if(n > m) swap(n,m);
        LL ans = 0;
        for(int i=1,last;i<=n;i=last+1)
        {
            last = min(n/(n/i),m/(m/i));
            ans += (n/i)*(m/i)*(sum[last]-sum[i-1]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

多项式乘法运算初级版——快速傅里叶变换

快速傅里叶变换在信息学竞赛中主要用于求卷积,或者说多项式乘法。我们知道,多项式乘法的普通算法时间复杂度

,通过快速傅里叶变换可以使时间降为,那么接下来会详细介绍快速傅里叶变换的原理。

首先来介绍多项式的两种表示方法,即系数表示法点值表示法。从某种意义上说,这两种方法是等价的。先设

(1)系数表示法

对于一个次数界为的多项式来说,其系数表示法就是一个由系数组成的向量,很

明显,这样的多项式乘法运算的时间复杂度为

(2)点值表示法

对于一个次数界为的多项式来说,其点值是个点值对所形成的集合

其中各不相同,并且当时,有。可以看出一个多项式可以有多种不同的点值

表示法,而通过这个不同的点值对可以表示一个唯一的多项式。而通过点值表示法来计算多项式的乘法,时间

复杂度为

从原则上来说,计算多项式的点值是简单易行的,因为我们只需要先选取个相异的点,然后通过秦九韶算法

以在时间内求出所有的,实际上如果我们的选得巧妙的话,就可以加速这一过程,使其运行时间变

根据多项式的系数表示法求其点值表示法的过程称为求值,而根据点值表示法求其系数表示法的过程称为插值

对于求卷积或者说多项式乘法运算问题,先是通过傅里叶变换对系数表示法的多项式进行求值运算,这一步的时

间复杂度为,然后在时间内进行点值相乘,再进行插值运算。

那么,接下来就是我们今天的重点了,如何高效地对一个多项式进行求值运算,即将多项式的表示法变为点值表示法。

如果选取单位复根作为求值点,则可以通过对系数向量进行离散傅里叶变换(DFT),得到相应的点值表示。同样地

也可以通过对点值对进行逆DFT运算,获得相应的系数向量。DFT逆DFT的时间复杂度均为

一. 求DFT

选取次单位复根作为来求点值是比较巧妙的做法。

次单位复根是满足的复数次单位复根恰好有个,它们是,为

了解释这一式子,利用复数幂的定义,值称为主次单位根,所有其

次单位复根都是次幂。

次单位复根在乘法运算下形成一个群,该群的结构与加法群相同。

接下来认识几个关于次单位复根的重要性质。

    (1)相消引理

对于任何整数,有

    (2)折半引理

如果且为偶数,则

    (3)求和引理

对任意整数和不能被整除的非零整数,有

回顾一下,我们希望计算次数界为的多项式

处的值,假定2的幂,因为给定的次数界总可以增大,如果需要,总可以添加值为零

的新的高阶系数。假定已知的系数形式为,对,定义结果

如下

向量是系数向量的离散傅里叶变换,写作

通过使用一种称为快速傅里叶变换(FFT)的方法,就可以在时间内计算出,而直接

计算的方法所需时间为FFT主要是利用单位复根的特殊性质。FFT方法运用了分治策略,它用

中偶数下标的系数与奇数下标的系数,分别定义了两个新的次数界为的多项式

则进一步有

这样处的值得问题就转换为求次数界为的多项式在点

处的值。由于在奇偶分类时导致顺序发生变化,所以需要先通过Rader算法进行

倒位序,在FFT中最重要的一个操作是蝴蝶操作,通过蝴蝶操作可以将前半部分和后半部分的值求出。

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1402

题意:大数乘法,需要用FFT实现。

代码:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>

using namespace std;
const int N = 500005;
const double PI = acos(-1.0);

struct Virt
{
    double r, i;

    Virt(double r = 0.0,double i = 0.0)
    {
        this->r = r;
        this->i = i;
    }

    Virt operator + (const Virt &x)
    {
        return Virt(r + x.r, i + x.i);
    }

    Virt operator - (const Virt &x)
    {
        return Virt(r - x.r, i - x.i);
    }

    Virt operator * (const Virt &x)
    {
        return Virt(r * x.r - i * x.i, i * x.r + r * x.i);
    }
};

//雷德算法--倒位序
void Rader(Virt F[], int len)
{
    int j = len >> 1;
    for(int i=1; i<len-1; i++)
    {
        if(i < j) swap(F[i], F[j]);
        int k = len >> 1;
        while(j >= k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k) j += k;
    }
}

//FFT实现
void FFT(Virt F[], int len, int on)
{
    Rader(F, len);
    for(int h=2; h<=len; h<<=1) //分治后计算长度为h的DFT
    {
        Virt wn(cos(-on*2*PI/h), sin(-on*2*PI/h));  //单位复根e^(2*PI/m)用欧拉公式展开
        for(int j=0; j<len; j+=h)
        {
            Virt w(1,0);            //旋转因子
            for(int k=j; k<j+h/2; k++)
            {
                Virt u = F[k];
                Virt t = w * F[k + h / 2];
                F[k] = u + t;     //蝴蝶合并操作
                F[k + h / 2] = u - t;
                w = w * wn;      //更新旋转因子
            }
        }
    }
    if(on == -1)
        for(int i=0; i<len; i++)
            F[i].r /= len;
}

//求卷积
void Conv(Virt a[],Virt b[],int len)
{
    FFT(a,len,1);
    FFT(b,len,1);
    for(int i=0; i<len; i++)
        a[i] = a[i]*b[i];
    FFT(a,len,-1);
}

char str1[N],str2[N];
Virt va[N],vb[N];
int result[N];
int len;

void Init(char str1[],char str2[])
{
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    len = 1;
    while(len < 2*len1 || len < 2*len2) len <<= 1;

    int i;
    for(i=0; i<len1; i++)
    {
        va[i].r = str1[len1-i-1] - '0';
        va[i].i = 0.0;
    }
    while(i < len)
    {
        va[i].r = va[i].i = 0.0;
        i++;
    }
    for(i=0; i<len2; i++)
    {
        vb[i].r = str2[len2-i-1] - '0';
        vb[i].i = 0.0;
    }
    while(i < len)
    {
        vb[i].r = vb[i].i = 0.0;
        i++;
    }
}

void Work()
{
    Conv(va,vb,len);
    for(int i=0; i<len; i++)
        result[i] = va[i].r+0.5;
}

void Export()
{
    for(int i=0; i<len; i++)
    {
        result[i+1] += result[i]/10;
        result[i] %= 10;
    }
    int high = 0;
    for(int i=len-1; i>=0; i--)
    {
        if(result[i])
        {
            high = i;
            break;
        }
    }
    for(int i=high; i>=0; i--)
        printf("%d",result[i]);
    puts("");
}

int main()
{
    while(~scanf("%s%s",str1,str2))
    {
        Init(str1,str2);
        Work();
        Export();
    }
    return 0;
}

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4609

题意:给定n条长度已知的边,求能组成多少个三角形。

分析:用一个num数组来记录次数,比如num[i]表示长度为i的边有num[i]条。然后对num[]求卷积,除去本身重

复的和对称的,然后再整理一下就好了。

代码:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#include <math.h>

using namespace std;
typedef long long LL;

const int N = 400005;
const double PI = acos(-1.0);

struct Virt
{
    double r,i;

    Virt(double r = 0.0,double i = 0.0)
    {
        this->r = r;
        this->i = i;
    }

    Virt operator + (const Virt &x)
    {
        return Virt(r+x.r,i+x.i);
    }

    Virt operator - (const Virt &x)
    {
        return Virt(r-x.r,i-x.i);
    }

    Virt operator * (const Virt &x)
    {
        return Virt(r*x.r-i*x.i,i*x.r+r*x.i);
    }
};

//雷德算法--倒位序
void Rader(Virt F[],int len)
{
    int j = len >> 1;
    for(int i=1; i<len-1; i++)
    {
        if(i < j) swap(F[i], F[j]);
        int k = len >> 1;
        while(j >= k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k) j += k;
    }
}

//FFT实现
void FFT(Virt F[],int len,int on)
{
    Rader(F,len);
    for(int h=2; h<=len; h<<=1) //分治后计算长度为h的DFT
    {
        Virt wn(cos(-on*2*PI/h),sin(-on*2*PI/h));  //单位复根e^(2*PI/m)用欧拉公式展开
        for(int j=0; j<len; j+=h)
        {
            Virt w(1,0);            //旋转因子
            for(int k=j; k<j+h/2; k++)
            {
                Virt u = F[k];
                Virt t = w*F[k+h/2];
                F[k] = u+t;      //蝴蝶合并操作
                F[k+h/2] = u-t;
                w = w*wn;      //更新旋转因子
            }
        }
    }
    if(on == -1)
        for(int i=0; i<len; i++)
            F[i].r /= len;
}

//求卷积
void Conv(Virt F[],int len)
{
    FFT(F,len,1);
    for(int i=0; i<len; i++)
        F[i] = F[i]*F[i];
    FFT(F,len,-1);
}

int a[N];
Virt F[N];
LL num[N],sum[N];
int len,n;

void Init()
{
    memset(num,0,sizeof(num));
    scanf("%d",&n);
    for(int i=0; i<n; i++)
    {
        scanf("%d",&a[i]);
        num[a[i]]++;
    }
    sort(a, a + n);
    int len1 = a[n-1] + 1;
    len  = 1;
    while(len < len1*2) len <<= 1;
    for(int i=0; i<len1; i++)
        F[i] = Virt(num[i],0);
    for(int i=len1; i<len; i++)
        F[i] = Virt(0,0);
}

void Work()
{
    Conv(F,len);
    for(int i=0; i<len; i++)
        num[i] = (LL)(F[i].r+0.5);
    len = a[n-1]*2;
    for(int i=0; i<n; i++)
        num[a[i]+a[i]]--;
    for(int i=1; i<=len; i++)
        num[i] >>= 1;
    sum[0] = 0;
    for(int i=1; i<=len; i++)
        sum[i] = sum[i-1] + num[i];
    LL cnt = 0;
    for(int i=0; i<n; i++)
    {
        cnt+=sum[len]-sum[a[i]];
        //减掉一个取大,一个取小的
        cnt-=(LL)(n-1-i)*i;
        //减掉一个取本身,另外一个取其它
        cnt-=(n-1);
        //减掉大于它的取两个的组合
        cnt-=(LL)(n-1-i)*(n-i-2)/2;
    }
    LL tot = (LL)n*(n-1)*(n-2)/6;
    printf("%.7lf\n",(double)cnt/tot);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        Init();
        Work();
    }
    return 0;
}

多项式乘法运算终极版——NTT(快速数论变换)

在上一篇文章中 http://blog.csdn.net/acdreamers/article/details/39005227 介绍了用快速傅里叶变

换来求多项式的乘法。可以发现它是利用了单位复根的特殊性质,大大减少了运算,但是这种做法是对复数系数的矩阵

加以处理,每个复数系数的实部和虚部是一个正弦及余弦函数,因此大部分系数都是浮点数,我们必须做复数及浮点数

的计算,计算量会比较大,而且浮点数的计算可能会导致误差增大。

今天,我将来介绍另一种计算多项式乘法的算法,叫做快速数论变换(NTT),在离散正交变换的理论中,已经证明在

复数域内,具有循环卷积特性的唯一变换是DFT,所以在复数域中不存在具有循环卷积性质的更简单的离散正交变换。

因此提出了以数论为基础的具有循环卷积性质的快速数论变换

回忆复数向量,其离散傅里叶变换公式如下

离散傅里叶逆变换公式为

今天的快速数论变换(NTT)是在上进行的,在快速傅里叶变换(FFT)中,通过次单位复根来运算的,即满

,而对于快速数论变换来说,则是可以将看成是的等价,这里是模素数

的原根(由于是素数,那么原根一定存在)。即

所以综上,我们得到数论变换的公式如下

数论变换的逆变换公式为

这样就把复数对应到一个整数,之后一切都是在系统内考虑。

上述数论变换(NTT)公式中,要求是素数且必须是的因子。由于经常是2的方幂,所以可以构造形

的素数。通常来说可以选择费马素数,这样的变换叫做费马数数论变换

这里我们选择,这样得到模的原根值为

题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1028

分析:题目意思就是大数相乘,此处用快速数论变换(NTT)实现。

代码:

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;
typedef long long LL;

const int N = 1 << 18;
const int P = (479 << 21) + 1;
const int G = 3;
const int NUM = 20;

LL  wn[NUM];
LL  a[N], b[N];
char A[N], B[N];

LL quick_mod(LL a, LL b, LL m)
{
    LL ans = 1;
    a %= m;
    while(b)
    {
        if(b & 1)
        {
            ans = ans * a % m;
            b--;
        }
        b >>= 1;
        a = a * a % m;
    }
    return ans;
}

void GetWn()
{
    for(int i=0; i<NUM; i++)
    {
        int t = 1 << i;
        wn[i] = quick_mod(G, (P - 1) / t, P);
    }
}

void Prepare(char A[], char B[], LL a[], LL b[], int &len)
{
    len = 1;
    int len_A = strlen(A);
    int len_B = strlen(B);
    while(len <= 2 * len_A || len <= 2 * len_B) len <<= 1;
    for(int i=0; i<len_A; i++)
        A[len - 1 - i] = A[len_A - 1 - i];
    for(int i=0; i<len - len_A; i++)
        A[i] = '0';
    for(int i=0; i<len_B; i++)
        B[len - 1 - i] = B[len_B - 1 - i];
    for(int i=0; i<len - len_B; i++)
        B[i] = '0';
    for(int i=0; i<len; i++)
        a[len - 1 - i] = A[i] - '0';
    for(int i=0; i<len; i++)
        b[len - 1 - i] = B[i] - '0';
}

void Rader(LL a[], int len)
{
    int j = len >> 1;
    for(int i=1; i<len-1; i++)
    {
        if(i < j) swap(a[i], a[j]);
        int k = len >> 1;
        while(j >= k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k) j += k;
    }
}

void NTT(LL a[], int len, int on)
{
    Rader(a, len);
    int id = 0;
    for(int h = 2; h <= len; h <<= 1)
    {
        id++;
        for(int j = 0; j < len; j += h)
        {
            LL w = 1;
            for(int k = j; k < j + h / 2; k++)
            {
                LL u = a[k] % P;
                LL t = w * (a[k + h / 2] % P) % P;
                a[k] = (u + t) % P;
                a[k + h / 2] = ((u - t) % P + P) % P;
                w = w * wn[id] % P;
            }
        }
    }
    if(on == -1)
    {
        for(int i = 1; i < len / 2; i++)
            swap(a[i], a[len - i]);
        LL Inv = quick_mod(len, P - 2, P);
        for(int i = 0; i < len; i++)
            a[i] = a[i] % P * Inv % P;
    }
}

void Conv(LL a[], LL b[], int n)
{
    NTT(a, n, 1);
    NTT(b, n, 1);
    for(int i = 0; i < n; i++)
        a[i] = a[i] * b[i] % P;
    NTT(a, n, -1);
}

void Transfer(LL a[], int n)
{
    int t = 0;
    for(int i = 0; i < n; i++)
    {
        a[i] += t;
        if(a[i] > 9)
        {
            t = a[i] / 10;
            a[i] %= 10;
        }
        else t = 0;
    }
}

void Print(LL a[], int n)
{
    bool flag = 1;
    for(int i = n - 1; i >= 0; i--)
    {
        if(a[i] != 0 && flag)
        {
            printf("%d", a[i]);
            flag = 0;
        }
        else if(!flag)
            printf("%d", a[i]);
    }
    puts("");
}

int main()
{
    GetWn();
    while(scanf("%s%s", A, B)!=EOF)
    {
        int len;
        Prepare(A, B, a, b, len);
        Conv(a, b, len);
        Transfer(a, len);
        Print(a, len);
    }
    return 0;
}
时间: 2024-08-25 16:59:39

数学(论)里的一些定理(莫比乌斯反演,傅立叶变换,数论变换...)的相关文章

JZYZOJ 1375 双亲数 莫比乌斯反演

http://172.20.6.3/Problem_Show.asp?id=1375 网上搜推理图. 有一段没有写莫比乌斯反演都快忘了..数学能力--,定理完全不会推,但是这道题整体来说应该是比较好写的(虽然我没写出来) 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namesp

莫比乌斯反演2

\documentclass[12pt,UTF8,titlepage]{article} \usepackage[colorlinks,linkcolor=blue]{hyperref} \usepackage{fontspec} \usepackage{xunicode} \usepackage{xltxtra} \usepackage{amstext} \usepackage{amsmath} \usepackage{amssymb} \usepackage{listings} \usepa

初学莫比乌斯反演

前言 莫比乌斯反演应该是比较难的一类数论题了. 关于它的许多性质,我也不怎么会证明(毕竟我数学差得要命). 学习这个算法时,在别人的博客中看到一句十分经典的话,在此特将其摘录如下: \(Excerpt\) 那些各种各样的性质与定理,大多是前人几年甚至几十年才得出来的,哪里是你几天就能理解并证明的. 莫比乌斯函数\(\mu\) 莫比乌斯反演中最重要的自然就是 莫比乌斯函数\(\mu\) 了. 定义 对于一个正整数\(d\),\(\mu(d)\)的定义如下: \[\mu(d)=\begin{case

POJ3094 Sky Code(莫比乌斯反演)

POJ3094 Sky Code(莫比乌斯反演) Sky Code 题意 给你\(n\le 10^5\)个数,这些数\(\le 10^5\),问这些这些数组成的互不相同的无序四元组(a,b,c,d)使得gcd(a,b,c,d)=1的四元组有多少? 解法 枚举一个约数\(k\),看看总共有多少个数\(S_k=\{x\}\)满足\(k|x\).那么可以保证(a,b,c,d)有的一个共同的因子是k,这样的四元组的个数就是 \[ F(k)={|S_k|\choose 4} \] 这样算会算重,比如枚举到

BZOJ 2154: Crash的数字表格 [莫比乌斯反演]

2154: Crash的数字表格 Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 2924  Solved: 1091[Submit][Status][Discuss] Description 今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple).对于两个正整数a和b,LCM(a, b)表示能同时被a和b整除的最小正整数.例如,LCM(6, 8) = 24.回到家后,Crash还在想着课上学的东西,为了研究

bnu——GCD SUM (莫比乌斯反演)

题目:GCD SUM 题目链接:http://www.bnuoj.com/v3/problem_show.php?pid=39872 算法:莫比乌斯反演.优化 1 #include<stdio.h> 2 #define N 100001 3 typedef long long LL; 4 bool pri[N]={0}; 5 int prim[N],po=0; 6 int mu[N]; 7 LL f[N],ff[N]; //缩短时间 8 /* 9 莫比乌斯函数mu[i]的定义: 10 1. 如

莫比乌斯反演介绍

转自:http://blog.csdn.net/acdreamers/article/details/8542292 莫比乌斯反演在数论中占有重要的地位,许多情况下能大大简化运算.那么我们先来认识莫比乌斯反演公式. 定理:和是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论 在上面的公式中有一个函数,它的定义如下: (1)若,那么 (2)若,均为互异素数,那么 (3)其它情况下 对于函数,它有如下的常见性质: (1)对任意正整数有 (2)对任意正整数有 1 void Init()

【bzoj3601】一个人的数论 莫比乌斯反演+高斯消元

题目描述 题解 莫比乌斯反演+高斯消元 (前方高能:所有题目中给出的幂次d,公式里为了防止混淆,均使用了k代替) #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const ll mod = 1000000007; ll a[110][110] , p[1010] , v[1010]; ll pow(ll x , ll

【bzoj2154】Crash的数字表格 莫比乌斯反演

题目描述 今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple).对于两个正整数a和b,LCM(a, b)表示能同时被a和b整除的最小正整数.例如,LCM(6, 8) = 24.回到家后,Crash还在想着课上学的东西,为了研究最小公倍数,他画了一张N*M的表格.每个格子里写了一个数字,其中第i行第j列的那个格子里写着数为LCM(i, j).一个4*5的表格如下: 1  2  3  4  5 2  2  6  4  10 3  6  3  12 15 4