oi初级数学知识

一、先是一些整除的性质

•整除:若a=bk,其中a,b,k都是整数,则b整除a,记做b|a。
•也称b是a的约数(因数),a是b的倍数
•显而易见的性质:
•1整除任何数,任何数都整除0
•若a|b,a|c,则a|b+c, a|b-c
•若a|b,则对任意整数c,a|bc
•传递性:若a|b,b|c,则a|c
例1:
•例题:[CF 762A]k-th divisor
•求n的第k小的约数。如果不存在输出-1
•1 ≤ n ≤ 10^15, 1 ≤ k ≤ 10^9
分析:这道题显然不能用O(n)次枚举出答案,如果能做到O(sqrt(n))就好了,事实上确实如此,因为每一个数的因数都是成对出现的,所以我们枚举sqrt(n)以内的因数就能得到范围以外的所有因数,将所得答案排序即可.
二、然后是一些质数和合数的性质:
•若大于1的正整数p仅有两个因子1和p,则称p是一个质数(素数)。
•否则,若p>1,则称p是一个合数。
•1不是质数也不是合数
•若n是一个合数,则n至少有1个质因子。因此其中最小的质因子一定不大于sqrt(n)
•质数有无穷多个。不大于n的质数约有n/ln(n)个。
唯一分解定理:把正整数n写成质数的乘积(即n=p1p2p3...pk,其中pi为质数且单调不减),这样的表示是唯一的。
例2:
•例题:[CF 776B]Sherlock and his girlfriend
•n个点,标号2..n+1,
•给这些点染色,要求若a是b的质因子,则a和b的颜色不同。
•求一种颜色数最少的方案
•n≤10^100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 分析:这个数据范围是吓你的,因为看到这个数据范围根本就无从下手嘛,连O(logn)都跑不过,除非O(1),不过确实是O(1),仔细分析一下,我们只需要2种颜色即可,质数一种颜色,合数一种颜色就可以了,原因就是合数的质因数是质数,而质数没有因数。
关于怎么分解质因数:
void fenjie(int x)
{
    for (int i = 2; i <= sqrt(x); i++)
    {
        while (x % i == 0)
        {
            if (!vis[i])
            {
                vis[i] = 1;
                ans[++tot] = i;
            }
            x /= i;
        }
    }
    if (x > 1)
        ans[++tot] = x;
}

分析:这个算法的关键有三个:1.如果除以一个因数就把它除尽. 2.记录这个数有没有进入答案. 3.最后可能还剩下一个数,至于为什么能剩下一个数,我们有一个很重要的定理:每一个数的质因数只有一个大于sqrt(n)的,根据唯一分解定律可以很容易证明.
下面是同余和带余除法的相关知识:

•对于整数a,b,b>0,则存在唯一的整数q,r,满足a=bq+r,其中0r<b
•其中称q为商、r为余数。
•余数用a mod b表示。
•若两数a,b除以c的余数相等,则a,bc同余记做ab(mod c)
性质:ab(mod c)c|a-b等价
•推论:若a≡b(mod c),d|c,则a≡b(mod d)
例3:奶牛分厮:传送门(应用了第五行的性质)
三、下面是一些最大公约数的知识:
•一些性质:
•(a,a)=(0,a)=a
•若a|b,则(a,b)=a
•(a,b)=(a,a+b)=(a,ka+b)
•(ka,kb)=k·(a,b)
•(a,b,c)=((a,b),c)
•若(a,b)=1,则称a,b互质(互素)
•互质的两个数往往有很好的性质
我们来证明一下第三条:
直接证明(a,b) = (a,a+b)很难,但是我们如果能证明a,b的因数和a,a+b的因数完全相同就好了,我们假设任意一个a,b的公因数p,p是a,b的因数,那么p也是a+b的因数,这样完成了从(a,b)向(a,a+b)的推导,至于如何反过来推导,将过程稍加修改一下即可,那么第二个等式也很容易出来了。这个证明利用的是整除的性质和我们的假设:任意的因数。
•例4:[CF 664A]Complicated GCD
•求gcd(a, a+1, a+2, ..., b)
•1≤a≤b≤10^100
分析:这个数据范围还是吓你的,可以很快的知道(a,a+1) = 1,任意两个相邻的正整数的gcd都为1,然后利用最后一条性质即可推出答案为1.至于为什么(a,a+1)=1呢?可以利用欧几里得算法:(a+1,a) = (a,1)= 1.
•例5:[CF 757B]Bash's Big Day
•给定n个正整数{ai}
•求一个子集S,满足gcd(S1, ..., Sk)>1,同时|S|尽可能大。
•1≤n,ai≤10^5
分析:一个一个去分解质因数肯定是不行的,但是我们可以利用像dp中的刷表法一样,从已知状态向未知状态扩展,假设我们枚举因数2,我们可以从2开始枚举有哪些数既是2的倍数又是集合中的数,这样我们枚举质数到amax即可,但是这个算法复杂度是O(n^2)的,会TLE,有没有什么优化方法呢?
     其实对于这种枚举的优化,我们无非是减少枚举层数或者避免枚举一定不会有解的答案,可以发现,我们只需要枚举当前质因数的倍数就好了,这样才能满足2个条件.这样的话算法复杂度是O(max(ai)*In(max(ai))).
四、欧几里德算法
•又称辗转相除法
•迭代求两数gcd的做法
•由(a,b)=(a,ka+b)的性质:gcd(a,b)=gcd(b, a mod b)
•容易证明这么做的复杂度是O(log n)
int gcd(int a, int b) {
    if (b==0) return a;
    return gcd(b, a % b);
} 
五、•裴蜀定理
(a,b)=d,则对任意整数x,y,有d|ax+by成立
•特别地,一定存在x,y满足ax+by=d
•等价的表述:不定方程ax+by=c(a,b,c为整数)有解的充要条件为(a,b)|c
•推论:a,b互质等价于ax+by=1有解
这个定理是为什么不定方程有解的理论依据.
六、扩展欧几里德算法
 扩展欧几里得算法主要是用来解决形如ax+by=c这类方程的问题的,考虑怎么解这个方程,利用欧几里得算法,我们可以把原方程变为:
bx'+(a - pb)y' = c,变为a和b的主元方程:ay' + b(x' - py'),其中p是a/b得到的商,那么我们就能得到x=y',y = x'-py',这样一直计算下去,可以
借助欧几里得算法的结果得到x''=1,y'' = 0,传递上来,就能得到x,y的值.我们可以考虑递归实现这个算法:
 
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1;
        y = 0;
        return a;
    }
    int r = exgcd(b, a%b, x, y);
    int t = x;
    x = y;
    y = t - a / b*y;
    return r;
}


这只是一组特解,如果我们要求出所有解该怎么办?可以得到一个解系:x = x0 + kb,y = y0 - ka。

不过运用这一个算法有个重要的前提,我们要求解的ax+by=c的c必须等于gcd(a,b),如果不等于该如何处理呢,可以看我的另一篇博客:青蛙的约会:传送门

另外,第一件事要先判断有没有解,利用裴蜀定理即可.

七、逆元

ax1 (mod b),则称xa关于模b的逆元,常记做a^(-1)
•回忆同余的性质。上式等价于ax+by=1
•如何求逆元?等价于解方程ax+by=1
•因此逆元不一定存在:
存在的充要条件为(a,b)=1
推论:p是质数,p不整除a,则ap的逆元存在。
 •结论:在[0,b)的范围内,a关于模b的逆元(若存在)是唯一的。
我来证明一下最后一个结论:如果存在两个逆元x1<x2<b,那么
ax1≡ax2≡1 (mod b) ,利用第一个同余式,a(x2-x1) % b == 0,
b不整除a,那么b整除x2-x1,而x2-x1 < b,所以矛盾,故结论成立.
int inv(int a, int b) {
    int x, y;
    exgcd(a, b, x, y);
    return x;
}

八、线性求逆元

如何O(n)求1~n模质数p的逆元?
我们肯定是要用O(1)复杂度来求出每一个数的逆元,那么一定要用到数学公式和之前推导出的值,我们怎么样才能使它强制出现以前计算过的逆元呢?
余数!我们知道余数小于除数,利用这个性质,我们可以得到:p≡0 (mod p) -----> iq+r≡0 (mod p),为了出现i的逆元,消掉r,我们同时乘i^(-1)r^(-1)即可,得到
qr^(-1)+i^(-1)≡0 (mod p),那么i的逆元就等于-qr^(-1).
for (inv[1] = 1, i = 2; i <= n; ++i)
    inv[i] = (p - p / i) * inv[p % i] % p;

程序里是p-p/i而不是p/i是为了避免负数.

•例6:组合数取模
•回答T次询问
•每次询问C(n, k) mod 998244353(一个质数)
•T≤10^5,0≤k≤n≤10^7
分析:大组合数是一个非常经典的求逆元的问题,我们知道%操作只有+,-,*三种操作,没有除法,但是可以利用逆元处理:a/b mod c = a * b^(-1) mod c,为什么是这样呢,我们把第一个式子乘b*b^(-1),因为1mod任何数还是1,所以等式成立,我们如果要求n^(-1)!,我们可以直接把1^(-1) * 2^(-1)*...*n^(-1),利用线性求逆元即可,因为有多个询问,保存下答案.
九、线性筛
•考虑一个经典问题:求不大于n的所有素数。
•Eratosthenes筛法:
•1.初始时令列表A={2,3,...,n};p=2
•2.枚举所有p的倍数(不包括p),并在A中删去这些数
•3.令p为A中的下一个数并跳转至(2)。如果不存在下一个则结束。
•4.算法结束时,A中剩下的数为不大于n的所有素数。
这个算法没什么好说的,就是求出补集,然后用全集减去补集就是我们所求的.
int sieve(int n, bool isprime[], int prime[]) {
    int tot = 0;
    for (int i = 2; i <= n; ++i) isprime[i] = 1;
    for (int i = 2; i <= n; ++i)
        if (isprime[i]) {
            prime[++tot] = i;
            for (int j = i + i; j <= n; j += i)
                isprime[j] = 0;
        }
    return tot;
}

复杂度O(nloglogn)
例7:求[1,10^7]内的所有素数。内存限制1MB

分析:内存只有1mb开不下这么大的数组,但是只要求一个答案,我们可以分段来做,假设我们分为k段,第i段为[l,r],我们枚举不超过sqrt(r)的素数并筛掉[l,r]的数即可,只是枚举素数总要保存的吧,那么我们用一种比较玄学的方法:保存[1,sqrt(10^7)]的素数,这样如果区间被覆盖在左边,直接就可以得到答案,如果被覆盖在右边,我们也稍微枚举一下就好了.

但是这个复杂度还是太高,能不能做到O(n)呢?其实可以,如果我们能够保证每一个数只被删除一次就可以了,怎么保证呢?我们利用数的最小分解来分析:比如一个数18,它的分解是唯一的,但是分解的数的排列并不是唯一的,比如2*3*3,3*2*3,我们只让它被第一个删除,怎么样才能做到呢?我们每次添加一个比当前已经分解的最小的质数更小的质数,比如已有了3*3,我们就只能添加2,如果已有2*3,我们就不能再继续搜了,为什么呢?如果我们添加一个5,5*2*3可以先被2*3*5搜到,这样被重复搜索了.这个线性筛的本质就是我们强行给它定一个搜索的顺序和限制,使得每一个数只能被删除一次.

int sieve(int n, int f[], int prime[]) {
    int tot = 0;
    for (int i = 2; i <= n; ++i) {
        if (!f[i]) prime[++tot] = f[i] = i;
        for (int j = 1; j <= tot; ++j) {
            int t = i * prime[j];
            if (t > n) break;
            f[t] = prime[j];
            if (f[i] == prime[j]) break;
        }
    }
}

这个代码中f[i]就是i被分解的最小质因数,如果我们枚举的素数正好等于f[i],也就不能继续了.

十、线性同余方程

•形如ax≡c (mod b)的方程,称为线性同余方程。
•等价于ax+by=c;因此有解条件为(a,b)|c
其实如果要求出x,我们只需要解出这个方程即可,其实还有简便做法:
如果(a,b) = 1,我们两边同时乘a^(-1),可以得到x≡a^(-1)c (mod b)
如果(a,b) != 1,因为(a,b)|c,所以说我们可以将a,b,c同时除以(a,b),然后
参照上面那种做法即可.
不过解方程和这种做法差不多,基本上都要做扩展欧几里得算法,不过后
一种做法可以得到一个定理:
任意的线性同余方程总可以判定为无解、或化为x≡a (mod m)的形式
十一、中国剩余定理
•考虑形如x≡ai (mod mi)的若干方程联立得到的方程组,如:
•x≡2 (mod 3) ............ (1)
•x≡3 (mod 5) ............ (2)
•x≡5 (mod 7) ............ (3)
•下面是一种可行的解法:
•由(1)设x=3y+2,代入(2)得到3y+2≡3(mod 5),解得y≡2(mod 5)
•设y=5z+2,代入(3)得到3(5z+2)+2≡5(mod 7),解得z≡4(mod 7)
•设z=7k+4,则x=3(5(7k+4)+2)+2=105k+68
•因此x≡68(mod 105)
 这个就相当于不同的换元,最终化简得到答案,不过这样做实在是太麻烦,有没有更简单一点的方法呢?
我们可以用中国剩余定理!
这个定理非常的巧妙,对于同余方程组x≡ai(mod mi) (i=1...n),若mi两两互质,则x在mod M下有唯一解。这里M=m1m2...mn,
我们先看一下怎么构造解:设Mi = M/mi,那么(M,Mi) = 1,有关于模mi的逆元存在,设Mi的逆元为ti,那么Mi*ti ≡ 1 (mod mi), 
对于任意一个mj,j!=i,因为Mi包含了因数mj,所以Mi*ti ≡ 0 (mod mj),这样我们排除了其它模数的干扰,这两个式子前面同时乘上ai,得到:
aiMiti≡ai (mod mi), aiMiti≡0 (mod mj) (j≠i),把得到的第一个式子累加起来,就得到了解x≡a1M1t1+a2M2t2+...+anMntn (mod M).
 
int CRT(const int a[], const int m[], int n) {
    int M = 1, ret = 0;
    for (int i = 1; i <= n; ++i) M *= m[i];
    for (int i = 1; i <= n; ++i) {
        int Mi = M / m[i], ti = inv(Mi, m[i]);
        ret = (ret + a[i] * Mi * ti) % M;
    }
    return ret;
}

例8:今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?

分析:

用中国剩余定理构造解。
ai = {2, 3, 2}, mi = {3, 5, 7}, M = 3 * 5 * 7 = 105
Mi = {105 / 3, 105 / 5, 105 / 7} = {35, 21, 15}
mi = {inv(35, 3), inv(21, 5), inv(15, 7)} = {2, 1, 1}
x ≡ 2 * (35 * 2) + 3 * (21 * 1) + 2 * (15 * 1)
x ≡ 233 ≡ 23 (mod 105)
 例9:
•组合数取模2
•回答T次询问,
•每次询问C(n, k) mod 1029471131
•1029471131 = 13*317*249811
•T≤105, 0≤k≤n≤1018
 分析:我们平常mod一个数都是mod质数,可是这次是个合数我们该如何解决呢?其实可以发现这个就是个中国剩余
定理的模板题,我们求出这个数mod13,mod317,mod249811的值,可以发现这3个数都是互质的,然后我们利用中国
剩余定理就能解出来.
 
这个定理可以将k和n快速缩小的p以内的范围中,以便减少复杂度.缩小后直接用组合数公式计算,同时线性求一下逆元即可.
十二、欧拉函数
 

第二个证明的n=p1^k1*p2^k2*...*pn^kn,不等于第一个证明中的n,当初为此迷惑了半天......

由此可以看出积性函数可以先拆成乘积的形式然后再合并就能得到公式.

欧拉函数的性质:1.phi(p^k)=(p-1)*p^(k-1)  2.积性函数(当a,b互质时!)  3.当b是质数,a%b==0,phi(a*b)=phi(a)*b,显然也可以利用积性函数来得到.

代码:

int phi(int x) {
    int ret = x;
    for (int i = 2; i * i <= x; ++i)
        if (x % i == 0) {
            while (x % i == 0) x /= i;
            ret = ret / i * (i - 1);
        }
    if (x > 1) ret = ret / x * (x - 1);
    return ret;
}

这份代码先除后乘的原因是防止溢出,我们在比赛的时候,如果数据范围大也要这么处理!

其实欧拉函数也可以像筛素数那样打一个表出来,具体的实现:

for(i=1; i<=maxn; i++)
    p[i]=i;
for(i=2; i<=maxn; i+=2)
    p[i]/=2;
for(i=3; i<=maxn; i+=2)
    if(p[i]==i)
    {
        for(j=i; j<=maxn; j+=i)
            p[j]=p[j]/i*(i-1);
    }

 

 

 例10:

                                                                                                                                                                                    2705: [SDOI2012]Longge的问题

Time Limit: 3 Sec  Memory Limit: 128 MB
Submit: 3160  Solved: 1973
[Submit][Status][Discuss]

Description

Longge的数学成绩非常好,并且他非常乐于挑战高难度的数学问题。现在问题来了:给定一个整数N,你需要求出∑gcd(i, N)(1<=i <=N)。

Input

一个整数,为N。

Output

一个整数,为所求的答案。

Sample Input

6

Sample Output

15

HINT

 

【数据范围】

对于60%的数据,0<N<=2^16。

对于100%的数据,0<N<=2^32。

 

 

Source

round1 day1

分析:如果直接暴力求gcd,n^2的枚举时间加上每次求gcd的时间,直接爆掉,那么能不能用更好的方法做呢?

       我们可以换个思路,要求Σgcd(i,n),我们假设gcd(i,n) = g,也就是我们要求以g为最大公约数的(i,n)有多少对,然后g对答案的贡献就是g*个数,显然,这个g是n的约数,对于约数的枚举我们有一个技巧,就是只枚举到它的sqrt即可,和它成对的一个约数就是n/i,前提是i != sqrt(n).如果g = 1,我们可以直接用欧拉函数来统计,如果g != 1呢?那么我们可以把i,n写作i = i'*g,n = n'*g,i和n同时除以g,那么n和i就互质了,这样直接用欧拉函数就可以解决问题了.

 

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

long long n,ans;

long long phi(long long x)
{
    long long res = x;
    for (long long i = 2; i <= sqrt(x); i++)
    {
        if (x % i == 0)
        {
            while (x % i == 0)
                x /= i;
            res = res / i * (i - 1);
        }
    }
    if (x > 1)
        res = res / x * (x - 1);
    return res;
}

int main()
{
    scanf("%lld", &n);
    for (long long i = 1; i <= sqrt(n); i++)
    {
        if (n % i == 0)
        {
            ans += i * phi(n / i);
            if (i * i < n)
                ans += n / i * phi(i);
        }
    }
    printf("%lld\n", ans);

    return 0;
}

 例11:

2186: [Sdoi2008]沙拉公主的困惑

Time Limit: 10 Sec  Memory Limit: 259 MB
Submit: 4549  Solved: 1567
[Submit][Status][Discuss]

Description

  大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。

Input

第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n

Output

共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值

Sample Input

1 11
4 2

Sample Output

1

数据范围:
对于100%的数据,1 < = N , M < = 10000000
分析:这道题又卡时间又卡空间......
       首先这道题要求互质的个数肯定要用欧拉函数来做,但是欧拉函数只能处理[1,m!]内的数,那么(m!,n!]内的数我们该怎么处理呢?要做出这道题,我们需要知道一个性质,如果x与m!互质,那么x+m!与m!互质,x+n*m!也与m!互质,所以我们处理一下[1,m!]内与m!互质的数的个数,然后利用这个性质就可以求出答案为φ(m!) * (n!) / (m!),这个φ(m!)可以化简一下:m!*π((pi-1) / (pi)),带入式子,得到答案为(n!) * π((pi - 1)/pi).
       因为有多个询问,所以我们先预处理出最大范围内的质数和逆元还有!,处理的方法是线性筛和线性求逆元,这里有一个优化就是能开bool尽量开bool,我之前开成long long就T了.
       现在还有一个问题就是爆空间,我们可以先打一个程序处理出1~10000000内的质数,可以发现质数的个数大约是664580个,数组刚好开这么大就可以过了.
 
#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn = 10000001;

int t, p,n,m;
long long f[maxn], ans[maxn],prime[664580],niyuan[maxn];
bool vis[maxn];

void init()
{
    long long tot = 0;
    for (int i = 2; i <= maxn; i++)
    {
        if (!vis[i])
            prime[++tot] = i;
        for (int j = 1; j <= tot; j++)
        {
            if (prime[j] * i > maxn)
                break;
            vis[prime[j] * i] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
    f[1] = 1;
    for (int i = 2; i <= maxn; i++)
        f[i] = f[i - 1] * i % p;
    niyuan[1] = 1;
    for (int i = 2; i <= maxn && i < p; i++)
        niyuan[i] = (p - p / i) * niyuan[p % i] % p;
    ans[1] = 1;
    for (int i = 2; i <= maxn; i++)
    {
        if (!vis[i])
            ans[i] = ans[i - 1] * (i - 1) % p * niyuan[i % p] % p;
        else
            ans[i] = ans[i - 1];
    }
}

int main()
{
    scanf("%d%d", &t, &p);
    init();
    for (int i = 1; i <= t; i++)
    {
        scanf("%d%d", &n, &m);
        printf("%d\n", f[n] * ans[m] % p);
    }

    return 0;
}

 十三、欧拉定理

不过求逆元一般是用扩展欧几里得来做,常数小,效率高,这两种方法的限制较多.

 

欧拉定理一个非常厉害的应用就是可以降低次数,一般的特征是a^b mod p,b非常大(甚至是正无穷),φ(p)比较小.
例12:

3884: 上帝与集合的正确用法

Time Limit: 5 Sec  Memory Limit: 128 MB
Submit: 2462  Solved: 1093
[Submit][Status][Discuss]

Description

 
根据一些书上的记载,上帝的一次失败的创世经历是这样的:
第一天, 上帝创造了一个世界的基本元素,称做“元”。
第二天, 上帝创造了一个新的元素,称作“α”。“α”被定义为“元”构成的集合。容易发现,一共有两种不同的“α”。
第三天, 上帝又创造了一个新的元素,称作“β”。“β”被定义为“α”构成的集合。容易发现,一共有四种不同的“β”。
第四天, 上帝创造了新的元素“γ”,“γ”被定义为“β”的集合。显然,一共会有16种不同的“γ”。
如果按照这样下去,上帝创造的第四种元素将会有65536种,第五种元素将会有2^65536种。这将会是一个天文数字。
然而,上帝并没有预料到元素种类数的增长是如此的迅速。他想要让世界的元素丰富起来,因此,日复一日,年复一年,他重复地创造着新的元素……
然而不久,当上帝创造出最后一种元素“θ”时,他发现这世界的元素实在是太多了,以致于世界的容量不足,无法承受。因此在这一天,上帝毁灭了世界。
至今,上帝仍记得那次失败的创世经历,现在他想问问你,他最后一次创造的元素“θ”一共有多少种?
上帝觉得这个数字可能过于巨大而无法表示出来,因此你只需要回答这个数对p取模后的值即可。
你可以认为上帝从“α”到“θ”一共创造了10^9次元素,或10^18次,或者干脆∞次。
 
一句话题意:

 

 

Input

 
接下来T行,每行一个正整数p,代表你需要取模的值

 

Output

T行,每行一个正整数,为答案对p取模后的值

 

Sample Input

3
2
3
6

Sample Output

0
1
4

HINT

 

对于100%的数据,T<=1000,p<=10^7
分析:这道题是欧拉定理的经典应用,一开始看到题面的我想到的就是找规律,可是涉及到∞,脑洞太小想不出来,但是它给定的模数是有限的,我们能不能从这个模数下手呢?
 
       我们接下来有两种思路:1.把模数越变越小,到最后直接出结果,类似于递归的过程 2.把次数的无穷化为有限,一般而言可以用欧拉定理和费马小定理,但是这道题给的模数不一定满足费马小定理的条件,欧拉定理也不一定满足,但是我们可以用欧拉定理的推论来解决.这两个思路结合起来就是本题的解.
       那么我们设答案为f(p), --图片摘自Codeplay0314的博客.
       注意边界,φ(x)= 0,当x = 1的时,因为如果x=1,显然原题是无解的.
这道题也说明了:若存在,那么也一定存在。当m=1时,存在,所以

 

对于所有正整数m,都有存在.
#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

int t, p;

long long qpow(long long a, long long b, long long mod)
{
    long long res = 1;
    while (b)
    {
        if (b & 1)
            res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}

long long phi(int x)
{
    long long res = x;
    for (long long i = 2; i <= sqrt(x);i++)
    {
        if (x % i == 0)
        {
            while (x % i == 0)
                x /= i;
            res = res / i * (i - 1);
        }
    }
    if (x > 1)
        res = res / x * (x - 1);
    return res;
}

long long solve(int x)
{
    if (x == 1)
        return 0;
    long long t = phi(x);
    return qpow(2, solve(t) + t, x);
}

int main()
{
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &p);
        printf("%lld\n",solve(p));
    }
return 0;
}

十四、积性函数

 

线性筛与积性函数之间有一定的联系,如果题目要求我们求出质数的同时求出欧拉函数值,我们可以利用积性函数的性质在筛法中求出欧拉函数值:

 

void shai()
{
    phi[1] = 1;
    for (int i = 2; i <= maxn; i++)
    {
        if (!vis[i])
        {
            prime[++tot] = i;
            phi[i] = i - 1;
        }
        for (int j = 1; j <= tot; j++)
        {
            int t = prime[j] * i;
            if (t > maxn)
                break;
            vis[t] = 1;
            if (i % prime[j] == 0)
            {
                phi[t] = phi[i] * j;
                break;
            }
            else
            phi[t] = phi[i] * phi[j];
        }
    }
}

现在来解释一下代码:这个线性筛和之前的模板不太一样,原理是相同的,当i % prime[j] == 0的时候就代表找到了最小表示的最小质数,不能继续往下筛了,如果i是一个质数,那么它的phi肯定是i-1,如果i,prime[j]互质,那么利用积性函数的定义直接来就好了。

否则, 如果i刚好是prime[j]的倍数,则利用欧拉函数的第三条性质即可.

 例13:

2818: Gcd

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 5930  Solved: 2636
[Submit][Status][Discuss]

Description

给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的
数对(x,y)有多少对.

 

Input

一个整数N

Output

如题

Sample Input

4

Sample Output

4

HINT

 

hint

对于样例(2,2),(2,4),(3,3),(4,2)


1<=N<=10^7

 

Source

湖北省队互测

分析:这道题是比较典型的gcd(a,b) = c求个数之类的问题,做法和这道题:传送门差不多,首先我们假设gcd(a,b) = g,这样不能直接统计,那么我们可以得到a = a' * g,b = b' * g, (a',b') = 1,同时约掉g,就变成了求互质的数的个数。不过这和bzoj2705有点不太一样,它并没有一个确定的数,不过我们可以分类讨论.

       1.如果x < y,那么从1到maxn枚举y,答案就是Σφ(y).

       2.如果x > y,一样的,不过答案就是Σφ(x).

       3.如果x = y,那么情况只有一种:x = y = 1,但是这种情况已经在上面两种情况中被算了,所以总的答案就是2*Σφ(i) - 1 (1 <= i <= maxn)

那么这个maxn就很显然了,就是n除以当前的g.

这个题的数据范围有点大,需要用到线性算法,我们既要求出质数,又要推出欧拉函数值,那么直接用线性筛就能解决.

 

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

int n,tot = 0;
const int maxn = 10000010;
int p[maxn], prime[maxn], f[maxn];
long long sum[maxn];
long long ans;

void init()
{
    p[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!f[i]) {
            prime[++tot] = f[i] = i;
            p[i] = i - 1;
        }
        for (int j = 1; j <= tot; ++j) {
            long long t = i * prime[j];
            if (t > n) break;
            f[t] = prime[j];
            p[t] = p[i] * (prime[j] - (prime[j] < f[i]));
            if (prime[j] == f[i]) break;
        }
    }

}

int main()
{
    scanf("%d", &n);
    init();
    for (int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + p[i];
    for (int i = 1; i <= tot; i++)
        ans += 2 * sum[n / prime[i]] - 1;
    printf("%lld", ans);

    return 0;
}

 例14:

bzoj2721 [Violet 5]樱花

 

分析:这道题对于我这种蒟蒻来说还是很有难度啊。

     思路非常巧妙,既然不定方程要有有限个数解,那么这个肯定会对解有所限制,也就是本题中的正整数.这个时候我们要表示出方程中的一个根x,设z = n!,那么x=yz/(y-z),这样的话不能得到答案,我们要得到的式子一定是分母只能有乘积的形式,并且同一个字母不能同时在分子分母中出现,因为我们就是利用正整数的整除性来求解的,可以看出x和y都大于z,所以我们设y = z + d,带入,就消掉了y,可以得到x = z^2/d + z,因为x是正整数,所以z^2/d必须是整数,所以d是z^2的因子,那么我们只需要求出z^2有多少个约数就好了.

     求约数的个数要用到乘法原理和线性筛,z可以表示为p1^k1 * p2^k2 * p3^k3*...*pn^kn这种形式,每个质因数可以选1到ki个或不选,而约数就是由不同的质因子通过相乘组合起来的,所以约数的个数就等于(k1 + 1)*(k2 + 1)*...*(kn + 1),而我们要求z^2的因子个数,总不可能直接平方吧......可以发现每个质因子的次数扩大了两倍,那么每个质因子就有2*ki + 1种选择,和上面一样直接乘法原理出答案.

     因为z = n!,所以枚举1到n中的质数i的倍数,看i出现了几次,就能得到ki.

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>

using namespace std;

const int mod = 1000000007;

int prime[1000010], tot, cnt[1000010],n;
bool vis[1000010];

long long ans = 1;

void init()
{
    for (int i = 2; i <= n; i++)
    {
        if (!vis[i])
            prime[++tot] = i;
        for (int j = 1; j <= tot; j++)
        {
            if (prime[j] * i > n)
                break;
            vis[prime[j] * i] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}

int main()
{
    scanf("%d", &n);
    init();
    for (int i = 1; i <= tot; i++)
        for (int j = prime[i]; j <= n; j += prime[i])
            for (int k = j; k % prime[i] == 0; k /= prime[i])
                cnt[i]++;
    for (int i = 1; i <= tot; i++)
        ans = (ans * 1LL * (cnt[i] * 2 + 1) % mod) % mod;

    printf("%lld\n", ans);
    return 0;
}

 

 例15:

D. Longest Subsequence
time limit per test
 2 seconds
memory limit per test
 256 megabytes
input
 standard input
output
 standard output

You are given array a with n elements and the number m. Consider some subsequence of a and the value of least common multiple (LCM) of its elements. Denote LCM as l. Find any longest subsequence of a with the value l ≤ m.

A subsequence of a is an array we can get by erasing some elements of a. It is allowed to erase zero or all elements.

The LCM of an empty array equals 1.

Input

The first line contains two integers n and m (1 ≤ n, m ≤ 10^6) — the size of the array a and the parameter from the problem statement.

The second line contains n integers ai (1 ≤ ai ≤ 10^9) — the elements of a.

Output

In the first line print two integers l and kmax (1 ≤ l ≤ m, 0 ≤ kmax ≤ n) — the value of LCM and the number of elements in optimal subsequence.

In the second line print kmax integers — the positions of the elements from the optimal subsequence in the ascending order.

Note that you can find and print any subsequence with the maximum length.

Examples
input
7 8
6 2 9 2 7 2 3
output
6 5
1 2 4 6 7
input
6 4
2 2 2 3 3 3
output
2 3
1 2 3
一句话题意:

 


分析:对于一个数k,它的所有约数的lcm肯定不大于k,也就是说对于每一个[1,m]中的数,我们只需要求出它的约数是a中的数的个数就好了,难道又要用筛法或质因数分解吗?其实不必,我们可以想到一个O(nm)的做法:枚举[1,m]中的每一个数,然后看看a中有多少个数整除它,显然T掉,能不能改进一下呢?
对于枚举的优化,要么是减少枚举层数,要么是减少对答案没有贡献的枚举,我们可以只用枚举整除数i的数,然后看看a中有没有这个数,这个操作可以用一个vis数组,只需要O(1)的时间查询。不过ai <= 10^9,我们似乎开不下这么大的vis数组,观察题目,发现m只有10^6,大于10^6的数我们可以不用考虑.
其实相对于枚举约数,枚举倍数更容易,我们从一个数扩展,将它的倍数的计数器累加即可,其实两种方法都可以.
#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn = 1000010;

int num[maxn], cnt[maxn],tot,a[maxn],ans,id,vis[maxn];
int n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        int t;
        scanf("%d", &t);
        a[i] = t;
        if (t > m)
            continue;
        ++num[t];
    }
    for (int i = 1; i <= n; i++)
    {
        if (!vis[a[i]])
        for (int j = a[i]; j <= m; j += a[i])
            cnt[j] += num[a[i]];
        vis[a[i]] = 1;
    }
    for (int i = 1; i <= m; i++)
        if (cnt[i] > ans)
        {
            ans = cnt[i];
            id = i;
        }
    printf("%d %d\n", id, ans);
    for (int i = 1; i <= n; i++)
        if (id % a[i] == 0)
            printf("%d ", i);

    return 0;
}

 

 

 

题意:已知n个数,第i个为ci,给定一个数x mod ci的结果,再给点一个k,问能不能知道x mod k的值?

分析:刚看题目的我一脸蒙蔽,对题意有点不理解,能的情况似乎有很多,我该从哪里下手呢?

     先从不能的情况来看,可以知道,如果不能知道x mod k的值,当且仅当有两个解x1,x2, x1 ≡ x2(mod ci)x1 ≢ x2 (mod k) 左边这个是不同余的意思,为什么是这样的呢?因为题目中说x mod k的值是唯一的,我们却会出现两个满足题意的x值 mod k的值不同,这就矛盾了。

     那么我们怎样求解这两个同余式呢?如果x1 ≡ x2(mod ci),那么(x1 - x2) % ci = 0,所以x1 - x2一定是ci的最小公倍数的倍数,然后对第二个式子变形一下:(x1 - x2) % k != 0,也就是说k不整除lcm{ci}那么这道题就变成了要我们求解lcm{ci}到底是不是k的倍数。

     但是直接求会lcm会爆掉啊,如果取模的话涉及到除法要求逆元复杂度又会爆炸,该怎么处理?正确的方法是分解质因数:将k表示为p1^k1 * p2 ^ k2 * ... *pn ^ kn的形式,如果lcm{ci}是k的倍数,那么p1^k1、p2^k2...pn^kn一定会全部出现在某些ci中,我们只需要在读入的时候检验一下打个标记就好了。一位大神说的对:lcm就是质因子的并集,gcd就是质因子的交集,遇到gcd、lcm,分解一下质因子不失为一种好的方法。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>

using namespace std;

int n, k, c,tot,prime[100010];
bool vis[100010];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 2; i <= sqrt(k); i++)
    {
        if (k % i == 0)
        {
            int t = 1;
            while (k % i == 0)
            {
                t *= i;
                k /= i;
            }
            prime[++tot] = t;
        }
    }
    if (k)
        prime[++tot] = k;
    for (int i = 1; i <= n; i++)
    {
        int c;
        scanf("%d", &c);
        for (int j = 1; j <= tot; j++)
            if (c % prime[j] == 0)
                vis[j] = 1;
    }
    for (int i = 1; i <= tot; i++)
        if (!vis[i])
        {
        printf("No\n");
        return 0;
        }
    printf("Yes\n");

    return 0;
}

 例17:

A. GCD Table
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

The GCD table G of size n × n for an array of positive integers a of length n is defined by formula

Let us remind you that the greatest common divisor (GCD) of two positive integers x and y is the greatest integer that is divisor of both xand y, it is denoted as . For example, for array a = {4, 3, 6, 2} of length 4 the GCD table will look as follows:

Given all the numbers of the GCD table G, restore array a.

Input

The first line contains number n (1 ≤ n ≤ 500) — the length of array a. The second line contains n2 space-separated numbers — the elements of the GCD table of G for array a.

All the numbers in the table are positive integers, not exceeding 109. Note that the elements are given in an arbitrary order. It is guaranteed that the set of the input data corresponds to some array a.

Output

In the single line print n positive integers — the elements of array a. If there are multiple possible solutions, you are allowed to print any of them.

Examples
input
4
2 1 2 3 4 3 2 6 1 1 2 2 1 2 3 2
output
4 3 6 2
input
1
42
output
42 
input
2
1 1 1 1
output
1 1 

题意:给出n个数之间任意两个数的gcd,求这n个数分别是多少.

分析:我们要从这个表中得到一些原数列的信息,显然,最大的那个数肯定是原数列中的数,然后把这个数去掉,并且将这个数和之前得到的数列中的数的gcd去掉,因为是一个矩形,所以一定会出现两次gcd,重复这种操作就能得到原数列中的数.

    至于实现,最好用map、hash,如果一个一个找数会浪费很多时间.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>

using namespace std;

map <int, int> m;

int a[510 * 510],n,tot,ans[510*510];

bool cmp(int x, int y)
{
    return x > y;
}

int gcd(int a, int b)
{
    if (!b)
        return a;
    return gcd(b, a % b);
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n * n; i++)
    {
        scanf("%d", &a[i]);
        m[a[i]]++;
    }
    sort(a + 1, a + n * n + 1,cmp);
    for (int i = 1; i <= n * n; i++)
    {
        if (!m[a[i]])
            continue;
        m[a[i]]--;
        for (int j = 1; j <= tot; j++)
            m[gcd(a[i], a[j])] -= 2;
        ans[++tot] = a[i];
    }
    for (int i = 1; i <= tot; i++)
        printf("%d ", ans[i]);

    return 0;
}

大BOSS

 

1876: [SDOI2009]SuperGCD

Time Limit: 4 Sec  Memory Limit: 64 MB
Submit: 3744  Solved: 1349
[Submit][Status][Discuss]

 

Description

 

Sheng bill有着惊人的心算能力,甚至能用大脑计算出两个巨大的数的GCD(最大公约 数)!因此他经常和别人比
赛计算GCD。有一天Sheng bill很嚣张地找到了你,并要求和你比 赛,但是输给Sheng bill岂不是很丢脸!所以你
决定写一个程序来教训他。

 

Input

 

共两行: 第一行:一个数A。 第二行:一个数B。
0 < A , B ≤ 10 ^ 10000。

 

Output

 

一行,表示A和B的最大公约数。

 

Sample Input

 

12
54

 

Sample Output

 

6

 

HINT

 

 

 

Source

 

 

分析:这道题是一道非常烦人的高精度问题,求gcd谁都会,但是直接用欧几里得算法,高精度取模直接gg,而且还是压位的,打出来估计要半天.那么换一种方法:更相减损术:

 

第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。        ----摘自百度百科
也就是说我们只需要设计乘法,除法和减法就可以了。高精度算法就相当于模拟我们算术一样,只不过用程序跑而已,步骤是先操作,再进位、补位,最后看数组的长度有没有改变.
这道题一定要压位来处理,不然会tle,压位后的操作与没有压位差不多,只不过mod的数变了,以前是逢10进1,现在是逢n进1.
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>

using namespace std;

const int mod = 100000000;

int tot;

struct node
{
    int x[2010], len;
}a,b;

void read(node &c)
{
    char s[10010];
    scanf("%s", s);
    int len = strlen(s),cnt = 0,chengji = 1;
    for (int i = len - 1; i >= 0; i--)
    {
        cnt = cnt + (s[i] - '0')* chengji;
        chengji *= 10;
        if ((len - i) % 8 == 0)
        {
            c.x[++c.len] = cnt;
            cnt = 0;
            chengji = 1;
        }
    }
    if (cnt != 0)
        c.x[++c.len] = cnt;
}

void div1()
{
    for (int i = a.len; i >= 1; i--)
    {
        if (a.x[i] % 2 == 1)
            a.x[i - 1] += mod;
        a.x[i] >>= 1;
    }
    while (a.x[a.len] == 0 && a.len > 1)
        a.len--;
}

void div2()
{
    for (int i = b.len; i >= 1; i--)
    {
        if (b.x[i] % 2 == 1)
            b.x[i - 1] += mod;
        b.x[i] >>= 1;
    }
    while (b.x[b.len] == 0 && b.len > 1)
        b.len--;
}

bool check()
{
    if (a.len != b.len)
        return false;
    for (int i = 1; i <= a.len; i++)
        if (a.x[i] != b.x[i])
            return false;
    return true;
}

bool cmp()
{
    if (a.len > b.len)
        return true;
    else
        if (a.len < b.len)
            return false;
    for (int i = a.len; i >= 1; i--)
    {
        if (a.x[i] > b.x[i])
            return true;
        else
            if (a.x[i] < b.x[i])
                return false;
    }
    return true;
}

void jian(node &c, node d)
{
    for (int i = 1; i <= c.len; i++)
    {
        if (c.x[i] >= d.x[i])
            c.x[i] -= d.x[i];
        else
        {
            c.x[i] = c.x[i] + mod - d.x[i];
            c.x[i + 1]--;
        }
    }
    while (c.x[c.len] == 0 && c.len > 1)
        c.len--;
}

void mul()
{
    for (int i = 1; i <= a.len; i++)
        a.x[i] <<= 1;
    for (int i = 1; i <= a.len; i++)
        if (a.x[i] >= mod)
        {
        a.x[i] -= mod;
        a.x[i + 1]++;
        }
    while (a.x[a.len + 1])
        a.len++;
}

void print()
{
    printf("%d", a.x[a.len]);
    for (int i = a.len - 1; i >= 1; i--)
        printf("%08d", a.x[i]);
    printf("\n");
}

int main()
{
    read(a);
    read(b);
    while (a.x[1] % 2 == 0 && b.x[1] % 2 == 0)
    {
        tot++;
        div1();
        div2();
    }
    while (!check())
    {
        if (cmp())
            jian(a, b);
        else
            jian(b, a);
        while (a.x[1] % 2 == 0)
            div1();
        while (b.x[1] % 2 == 0)
            div2();
    }
    while (tot--)
        mul();
    print();

    return 0;
}

 陆陆续续添加了18道题,难度基本上是noip提高组+,推荐阅读进阶博客:传送门,谢谢大家的阅读,如果发现有什么错误,请指出!qq:1339623011,愿不吝赐教!

 

 

 

 

 

 

 

 

 

 

 
 
 
 
 
 
 

 

 
 
posted @ 2017-08-09 22:15  zbtrs  阅读(1738)  评论(0编辑  收藏  举报