【全程NOIP计划】简单数论

基础知识与记号

\forall,\exist

整除:如果$a=bk ,其中,其中a,b,k都是整数,则都是整数,则b整除整除a,记作,记作b|a$

也称作b是a的约数(因数),a是b的倍数

如何求出n的所有约数?

如果a是n的约数,则n/a也是n的约数,且a和n/a中必有一个小于等于根号n

所以我们直接枚举1到根号n之间的所有数,判断是不是n的因数就可以了

a和b的最大公因数记为gcd(a,b)\gcd(a,b),或者(a,b)(a,b)

特殊地

1.(a,a)=(0,a)=a(a,a)=(0,a)=a

2.如果aba|b,则(a,b)=a(a,b)=a

3.(a,b)=(a,a+b)=(a,ka+b),(a,b)=(b,a mod b)(a,b)=(a,a+b)=(a,ka+b),(a,b)=(b,a \space mod \space b)

4.(ka,kb)=k(a,b)(ka,kb)=k*(a,b)

5.(a,b,c)=((a,b),c)(a,b,c)=((a,b),c)

6.如果(a,b)=1(a,b)=1则称a和b互质,记为aba \bot b

对于整数a,b,b>0b>0,则存在唯一的整数q,r,满足a=bq+ra=bq+r,其中0r<b0 \leq r <b

a÷b=qra \div b = q \dots \dots r

其中称q为商,r为余数,余数r用a mod ba \space mod \space b表示

如果有两个整数a满足除以b的余数为r,则我们称aa(mod b)a \equiv a'(mod \space b)

唯一分解定理

对于一个整数n,它可以唯一分解成n=pikin= \prod p_i^{k_i}

例如36可以唯一分解为36=22×3236=2^2 \times 3^2

定义域为整数的函数被称为数论函数

对于数论函数ff

1.如果ab,f(ab)=f(a)f(b)\forall a\bot b ,f(ab)=f(a)f(b),则称ff为积性函数

2.如果a,b,f(ab)=f(a)f(b)\forall a,b,f(ab)=f(a)f(b),则称ff为完全积性函数

例如f(x)=xf(x)=x就是一个完全积性函数

欧几里得算法

给定aba,b,快速算出(a,b)(a,b)

引理:若a<ba<b,则b mod a<b/2b \space mod \space a<b/2

不断运用(a,b)=(a,b mod a)(a,b)=(a,b\space mod \space a),直到某个数为0

至少运行log2a\log_2^a次或者log2blog_2^b次,就会有某个数变成0

1
2
3
4
int gcd(int a,int b)
{
return b? gcd(b,a%b):a;
}
拓展欧几里得算法

裴蜀定理

a,b,x,y,s.t.ax+by=(a,b)\forall a,b, \exist x,y,s.t. ax+by=(a,b)

使用构造法来证明这个东西

当b=0时,x=1,y=0

否则:
$
ax+by

\=(\lfloor \frac a b \rfloor*b+a \mod b)x+by
\=b(y+\lfloor \frac a b \rfloor x)+(a\mod b)
\=gcd(a,b)
如果已经求出来 如果已经求出来bx’+(a \mod b)y’=gcd(a,b)的解,则的解,则x=y’,y=x’-\lfloor \frac a b \rfloor y’$

1
2
3
4
5
6
7
8
9
10
void exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return ;
}
exgcd(b,a%b,y,x);
y-=a/b*x;
}

如何求出ax+by=(a,b)ax+by=(a,b)所有的解?

先用exgcd求出一组x0,y0

其他的解为:x=x0+kb(a,b),y=y0ka(a,b)x=x_0+k \frac b {(a,b)},y=y_0-k \frac a {(a,b)}

其中最小的正数x为(b(a,b)+x0modb(a,b))modb(a,b)(\frac b {(a,b)}+x_0 \mod \frac b {(a,b)}) \mod \frac b {(a,b)}

欧拉函数

ϕ(n)\phi(n)表示1~n中与n互质的数的个数,称为欧拉函数

例如1~12中,1,5,7,11和12互质,则ϕ(12)=4\phi(12)=4

怎么求?

n=pikin =\prod p_i^{k_i}ϕ(n)=n(11pi)=piki1(pi1)\phi(n)=n \prod(1-\frac 1 {p_i})=\prod p_i^{k_i-1}*(p_i-1)

枚举质因子计算就可以了,时间复杂度O(n)O( \sqrt{n})

(拓展)欧拉定理(欧拉函数)

欧拉定理:ab,aϕ(n)1(modn)\forall a \bot b ,a^{ \phi (n)} \equiv 1 (\mod n)

拓展欧拉定理:a,n,a2ϕ(n)aϕ(n)(mod n)\forall a,n,a^{2 \phi(n)} \equiv a^{\phi (n)}(mod \space n)

也就是说,b>ϕ(n)abab mod ϕ(n)+ϕ(n)(mod n)b> \phi(n) \to a^b \equiv a^{b \space mod \space \phi(n)+\phi(n)}(mod \space n)

P5110 块速递推

思路

先运用拓展欧拉定理 ,将b缩小到2ϕ(c)<2c2\phi(c)<2c的范围之内

ab=(ac)bcab mod ca^b=(a^{ \sqrt{c}})^{\lfloor \frac b {\sqrt{c}}\rfloor}*a^{b \space mod \space c},其中bc<2c,b mod c<c\lfloor \frac b {\sqrt{c}} \rfloor <2 \sqrt{c},b \space mod \space \sqrt{c}< \sqrt{c}

预处理(ac)i,aj(1i2c,1j<c)(a^{\sqrt{c}})^i,a^j(1\le i \le 2\sqrt{c},1 \le j <\sqrt{c})即可

实际上是一种分块的做法

可以预处理,然后再直接查询

实际上就是光速幂,具体模板题为LOJ.162.快速幂 2

通过题目给定的递推公式,我们首先可以推出来一个用来递推的矩阵
X=[23316660] X=\begin{bmatrix} 233 & 1 \\ 666 & 0 \end{bmatrix}
然后预处理一下

最后对于每个n,直接通过预处理的数据直接O(1)O(1)输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
using namespace std;
const int maxn=50005;
const int p=1e9+7;
unsigned long long T,res;
namespace Mker
{
unsigned long long SA, SB, SC;
void init()
{
scanf("%llu%llu%llu", &SA, &SB, &SC);
}
unsigned long long rand()
{
SA ^= SA << 32, SA ^= SA >> 13, SA ^= SA << 1;
unsigned long long t = SA;
SA = SB, SB = SC, SC ^= t ^ SA; return SC;
}
}
struct matrix
{
long long a,b,c,d;
matrix()
{
a=b=c=d=0;
}
matrix operator *(register matrix &s)
{
matrix temp;
temp.a=(a*s.a+b*s.c)%p;
temp.b=(a*s.b+b*s.d)%p;
temp.c=(c*s.a+d*s.c)%p;
temp.d=(c*s.b+d*s.d)%p;
return temp;
}
}base,m1[maxn],m2[maxn];

int main()
{
cin>>T;
Mker::init();
//初始化矩阵
base.a=233;
base.b=666;
base.c=1;
/*进行矩阵递推*/
m1[1]=base;
int len=sqrt(1e9+6);
for(int i=2;i!=len;i++)
m1[i]=m1[i-1]*base;

m2[1]=m1[len-1]*base;
m1[0].a=m1[0].d=1;
m2[0].a=m2[0].d=1;

for(int i=2;i<=len+1;i++)
m2[i]=m2[i-1]*m2[1];

while(T--)
{
matrix ans;
ans.a=1;
int n=Mker::rand()%1000000006;
if(n<=1)
res^=n;
else
{
ans=ans*m2[(n-1)/len]*m1[(n-1)%len];
res^=ans.a;
}
}
cout<<res<<'\n';
return 0;
}
逆元

如果ax1(mod b)ax \equiv 1(mod \space b),则称x为a关于模b的逆元,通常记作a1a^{-1}

分数取模:abab1(mod p)\frac ab \equiv a*b^{-1}(mod \space p),

a1a^{-1}存在的充要条件是(a,b)=1(a,b)=1

[0,b)[0,b)的范围诶,a关于模b的逆元(如果存在的话),是唯一的

等价于求ax+by=1ax+by=1,课可以直接使用拓展欧几里得解决

如果b是质数,则ϕ(b)=b1,aab2=ab11(mod b)\phi(b)=b-1,a*a^{b-2}=a^{b-1}\equiv 1(mod \space b),所以逆元为ab2a^{b-2},使用快速幂计算

如何在O(n)O(n)的时间内求1~n内的模质数p的所有逆元呢?

$i^{-1} \equiv - \lfloor \frac pi \rfloor (p \space mod \space i)^{-1} \iff p \space mod \space i \equiv -\lfloor \frac pi \rfloor *i \iff \lfloor \frac pi \rfloor *i+p \space mod \space i =p \equiv (mod \space p) $

威尔逊定理

(p1)!1(mod p)(p-1)! \equiv -1 (mod \space p)

x21(mod p)x^2 \equiv 1 (mod \space p)的解仅有x1,x1(mod p)x \equiv 1,x \equiv -1(mod \space p)

所以2~p-2和逆元两两对应

剩下1(p1)1(mod p)1*(p-1) \equiv -1 (mod \space p)

中国剩余定理

求解x,满足方程组
\bbox[yellow]{ \begin{cases} x \equiv 2 & \text{(mod 3)} \\ x \equiv 3 & \text{(mod 5)} \\ x \equiv 2 & \text{(mod 7)} \\ \end{cases} }
M=m1m2mn,Mi=M/miM=m_1m_2\dots m_n,M_i=M/m_i

显然有(Mi,mi)=1(M_i,m_i)=1,所以MiM_i关于模mim_i的逆元存在,把这个逆元设为tit_i

于是有Miti1(mod m),Miti0(mod mj)(ji)M_it_i \equiv 1 \text{(mod m)},M_it_i \equiv 0 (mod \space m_j)(j \not= i)

进一步有aiMitiai(mod mi),aiMiti0(mod mj)(ji)a_iM_it_i \equiv a_i(mod \space m_i),a_iM_it_i \equiv 0 (mod \space m_j)(j \not= i)

因此解为xa1M1t1+a2M2t2+anMntn(mod M)x \equiv a_1M_1t_1+a_2M_2t_2+\dots \dots a_nM_nt_n(mod \space M),且在模m意义下唯一解

拓展中国剩余定理

求解x,满足方程组xai(mod mi)x \equiv a_i(mod \space m_i),其中mim_i不一定两两互质

同样的,在mod lcm(mi)lcm(m_i)的意义下有唯一解

考虑如何每次将两个方程$\begin{cases} x \equiv a_1 \space mod \space m_1 \x \equiv a_2 \space mod \space m_2\end{cases} 合并成一个方程合并成一个方程x \equiv a(mod \space m =lcm(m_1,m_2))
\begin{cases} x\equiv a_1(mod \space m_1) \
x\equiv a_2(mod \space m_2)\end{cases} \iff
\begin{cases} x=a_1+k_1m_1 \
x=a_2+k_2m_2\end{cases}
\iff k_1m_1-k_2m_2=a_2-a_1
先用拓展欧几里得算法求出 先用拓展欧几里得算法求出k_1m_1-k_2m_2=gcd(m_1,m_2)的一组特解的一组特解K_1,K_2$

$ \begin{cases} x=a_1+K_1 \frac {a_2-a_1} {gcd(m_1,m_2)}m_1 \ x=a_2+K_2 \frac {a_2-a_1}{gcd(m_1,m_2)}m_2 \end{cases}可以发现两个不同的解x的差一定是可以发现两个不同的解x的差一定是lcm(m_1,m_2)$的倍数

所以xa1+K1a2a1gcd(m1,m2)=a2+K2a2a1gcd(m1,m2)m2(mod lcm(m1,m2))x \equiv a_1+K_1 \frac{a_2-a_1}{gcd(m_1,m_2)}=a_2+K_2 \frac {a_2-a_1}{gcd(m_1,m_2)}m_2(mod \space lcm(m_1,m_2))

其他常见的积性函数

μ(n)={0,0有平方因子(1)kn无平方因子,k为n的质因数个数 \mu(n)=\begin{cases} 0,&\text{0有平方因子} \\(-1)^k &\text{n无平方因子,k为n的质因数个数} \end{cases}

σk(n)=inik\sigma_k(n)=\sum_{i|n}i^k,σ0(n)\sigma_0(n)又写作d(n)d(n)表示n的约数个数(除数函数)

gcd(n,k)gcd(n,k)

完全积性函数:

1(n)=1,idk(n)=nk,ϵ(n)={1,n=10,n−̸11(n)=1,id_k(n)=n^k,\epsilon(n)=\begin{cases} 1,n=1 \\0,n \not-1\end{cases}

Nδ=pp1pδ1,NδN_{\delta}= \prod_pp^{\lfloor \frac 1 {p^{\delta}-1} \rfloor},N_{\delta}显然收敛

n>Nδn>N_{\delta}时,有:d(n)<n(1+δ)log2log lognd(n)<n^{(1+\delta)\frac {log2}{log\space logn}}

普通筛法

以下整数,代指>1的整数

基本思想:整数的整数倍是合数,时间复杂度O(nlogn)O(nlogn)

1
2
3
4
5
6
7
for(int i=2;i<=n;i++)
{
if(!vis[i])
p[++t]=i;
for(int j=2*i;j<=n;j+=i)
vis[j]=1;
}
埃氏筛

基本思想:素数的整数倍数为合数,时间复杂度O(n log logn)O(n\space log \space logn)

1
2
3
4
5
6
7
8
9
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
p[++t]=i;
for(int j=2*i;j<=n;j+=i)
vis[j]=true;
}
}
线性筛

基本思想:整数的素数倍数是合数

枚举整数的素数倍数,当枚举到的素数是当前整数的因数时候停止

设n的最小值因子为pnp_n,n会且仅在i=n/pni=n/p_n倍筛到

1
2
3
4
5
6
7
8
9
10
11
for(int i=2;i<=n;i++)
{
if(!vis[i])
p[++t]=i;
for(int j=1;j<=t&&i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0)
break;
}
}
区间筛

[a,b][a,b]中的所有质数,a,b1012,ba106a,b \leq 10^{12},b-a \leq 10^6

基本思想:埃氏筛

1.先筛出1b1到 \sqrt{b}的所有质数

2.用这些质数筛去[a,b][a,b]中的合数

线性筛求积性函数

对于每个合数n,在线性筛的过程中都会拆成npn\frac n {p_n}pnp_n

(pninf,n)=pnk(p_n^{\inf},n)=p_n^k,则npnk\frac n {p_n^k}pnkp_n^k是互质的

1.算出f(pnk)f(p_n^k)

2.对于其他合数,f(n)=f(npnk)f(pnk)f(n)=f(\frac n {p_n^k})*f(p_n^k)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//phi
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
p[++t]=i;
phi[i]=i-1;
}
for(int j=1;j<=t&&i*p[j]<=n;j++)
{
vis[i*p[j]]=true;
if(i%p[j])
phi[i*p[j]]=phi[i]*(p[j]-1);
else
{
phi[i*p[j]]=phi[i]*p[j];
break;
}
}
}

P2303 Longge的问题

思路

1.直接计算可以得65分,非常良心

我们设 t(x)t(x)1n1 - n 中与 nn 的最大公因数为 xx 的数的个数,即

t(x)=i=1n[gcd(i,n)=x]=i=1nx[gcd(i,nx)=1]t(x) = \sum_{i = 1}^n[\gcd(i, n) = x] = \sum_{i = 1}^{\lfloor\frac nx\rfloor}[\gcd(i, \lfloor\frac nx\rfloor) = 1]
考虑 ϕ(x)=i=1n[gcd(i,n)=1]\phi(x) = \sum\limits_{i = 1}^n[gcd(i, n) = 1]
t(x)=ϕ(nx)t(x) = \phi(\lfloor\frac nx\rfloor)
由于gcd(i,n)n\gcd(i, n) | n,考虑化简原式为:
i=1ngcd(i,n)=dndi=1n[gcd(i,n)=d]=dndϕ(nd)\sum_{i = 1}^n\gcd(i, n) = \sum_{d|n}d\sum_{i = 1}^n[\gcd(i, n) = d] = \sum_{d|n}d\phi(\frac nd)
对于所有 dnd | n 可以在 O(n)O(\sqrt n) 的时间复杂度内枚举,ϕ\phi 函数可以在 O(n)O(\sqrt n) 的时间复杂度内求出,故时间复杂度为 O(因数个数n)O(因数个数 * \sqrt n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
long long n;
int main()
{
scanf("%lld",&n);
long long ans=n;
for(register long long i=2;i*i<=n;++i)
{
if(n%i==0)
{
int b=0;
while(n%i==0)
{
b++;
n/=i;
}
ans/=i;
ans*=b*i-b+i;
}
}
if(n>1)
{
ans/=n;
ans*=n+n-1;
}
printf("%lld",ans);
return 0;
}

CF757B Bash’s Big Day

思路

gcd>1gcd>1,说明存在一个正整数d>1d>1,满足d整除s内的所有元素

枚举d=2maxaid=2 \to max_{a_i}并统计答案

V=maxaiV=max_{a_i},则复杂度为O(VlnV)O(VlnV)

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>using namespace std;const int maxn=100005;int n,tot,maxx=1;int a[maxn],fac[maxn];inline int read(){	int x=0,f=1;char ch=getchar();	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}	return x*f;}void record(int x){	for(int i=2;i*i<=x;i++)	{		if(x%i==0)		{			fac[i]++;			fac[x/i]++;		}		if(i*i==x)		fac[x/i]--;	}	fac[x]++;	return ;}int main(){	n=read(); 	for(int i=1;i<=n;i++)	{		a[i]=read();		record(a[i]);	}	sort(a+1,a+1+n);	for(int i=2;i<=a[n];i++)	maxx=max(maxx,fac[i]);	cout<<maxx<<endl;	return 0;}

CF776B Sherlock and his girlfriend

思路

注意到这是二分图,一边是质数,一边是合数

运用埃氏筛把合数都筛掉

然后质数和合数的颜色不同就可以了

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>using namespace std;int n;bool p[100005];int main(){	cin>>n;	p[0]=true,p[1]=true;	for(int i=2;i*i<=n+1;i++)	{		if(!p[i])		{			for(int j=i*2;j<=n+1;j+=i)			p[j]=true;		}	}	if(n<3)	cout<<1<<'\n';	else	cout<<2<<'\n';	for(int i=2;i<=n+1;i++)	cout<<(p[i]? 1:0)+1<<" ";	cout<<'\n';	return 0;}

P4139 上帝与集合的正确用法

思路

题目要求出的是2222 mod p2^{2^{2^{2…}}} \space mod \space p的值

根据扩展欧拉定理
ab{ab mod φ(p)+φ(p)(b>φ(p))ab(bφ(p)) (mod p) a^b\equiv \left\{\begin{aligned}a^{b \text{ mod }φ(p)+φ(p)}(b&>φ(p))\\a^b(b≤φ(p))\end{aligned}\right. \text{ (mod }p)
可以通过一个函数来递归求

可是题目中让求的是无限层,并不是有限层,怎么办啊?
不要紧,在一层层的递归中,pp一遍遍的被求其φφ值,最终必定会降到11。原因很简单,因为:φ(p)<p (p>1)φ(p)<p\space(p>1)
而且降的很快,所以递归很快就会结束

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>using namespace std;const int maxn=1e7+5;int T,tot;int pri[1000005],phi[maxn];bool vis[maxn];inline int read(){	int x=0,f=1;char ch=getchar();	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}	return x*f;}int ksm(int a,int b,int p){	int ans=1%p;	for( ;b;b>>=1)	{		if(b&1)		ans=(long long)ans*a%p;		a=(long long)a*a%p; 	}	return ans;}int dfs(int x){	if(x==1)	return 0;	int n=phi[x];	return ksm(2,dfs(n)+n,x);}void init(){	for(int i=2;i<=maxn;i++)	{		if(!vis[i])		pri[++tot]=i,phi[i]=i-1;		for(int j=1;j<=tot&&pri[j]*i<=maxn;j++)		{			vis[pri[j]*i]=1;			if(i%pri[j])			phi[i*pri[j]]=phi[i]*phi[pri[j]];			else			{				phi[i*pri[j]]=phi[i]*pri[j];			}		}	}}int main(){	init();	T=read();	while(T--)		cout<<dfs(read())<<'\n';	return 0;}

一道例题

题目

[1,107][1,10^7]内的所有素数,内存限制1MB

思路

[1,107][1,10^7]分成k段分别求解

对于区间[l,r][l,r],枚举不大于r\sqrt{r}的所有素数p,在[l,r][l,r]中筛去p的倍数

需要预处理[1,n][1,\sqrt{n}]的所有素数

时间复杂度kn+nloglognk\sqrt{n}+nlog^{log^n}

空间复杂度n+n/k\sqrt{n}+n/k

P2568 gcd

思路

线性筛出不大于NN的所有素数pp。问题就转化为求(x,y)=p(x,y)=p的个数

x=xp,y=ypx=x'p,y=y'p那么有(x,y)=1(x',y')=11x,yN/p1 \le x,y \le N/p

转化为求(x,y)=1(x,y)=11x,yn1 \le x,y \le n的个数

答案为2i=1nϕ(i)12\sum_{i=1}^n\phi(i)-1

线性筛筛出欧拉函数,预处理前缀和就可以了

然后对于每个小于等于NN的素数处理累加到答案里就可以了

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#define int long longusing namespace std;const int maxn=1e7+5;int pri[maxn],phi[maxn],s[maxn];bool vis[maxn];int n,tot;long long ans=0;void init(int x){	phi[1]=1;	for(int i=2;i<=x;i++)	{		if(!vis[i])		pri[++tot]=i,phi[i]=i-1;		for(int j=1;j<=tot&&pri[j]*i<=x;j++)		{			vis[pri[j]*i]=1;			if(i%pri[j])			phi[i*pri[j]]=phi[i]*phi[pri[j]];			else			{				phi[i*pri[j]]=phi[i]*pri[j];			}		}	}	for(int i=1;i<=x;i++)	s[i]=s[i-1]+phi[i];} signed main(){	cin>>n;	init(n);	for(int i=1;i<=tot;i++)	ans+=(2*s[n/pri[i]]-1);	cout<<ans<<'\n';	return 0;}

P2155 沙拉公主的困惑

思路

感谢@Siyuan

PS:越来越意识到开long long的重要性

答案是:
i=1n![gcd(i,m!)=1] \sum_{i=1}^{n!}[gcd(i,m!)=1]
我们类比gcd

我们知道nmn \ge m,所以我们可以把[1,n!][1,n!]分成若干段

准确地来说是 n!m!\dfrac {n!} {m!}段,每段的答案是一样的,这是由gcd的更相减损术得到的

然后我们就可以更换求和的上界
n!m!i=1m![gcd(i,m!)=1] \dfrac {n!} {m!}\sum_{i=1}^{m!}[gcd(i,m!)=1]
右边的就是1到m!中与m互质的数,也就是
φ(m!) \varphi(m!)
我们知道,设n=pikin =\prod p_i^{k_i},则φ(n)=n(11pi)=piki1(pi1)\varphi(n)=n \prod(1-\frac 1 {p_i})=\prod p_i^{k_i-1}*(p_i-1)

所以我们设m!=i=1kpicim!=\prod_{i=1}^kp_i^{c_i},可以得到:
φ(m!)=m!i=1kpi1pi \varphi(m!)=m! \prod_{i=1}^k \dfrac {p_i-1} {p_i}
所以讲这个东西带入上面的式子,答案就是
n!i=1kpi1pi n!\prod_{i=1}^k \dfrac {p_i-1} {p_i}
那个玩意儿我们可以用线性筛求

时间复杂度O(N+T)O(N+T)

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#define int long longusing namespace std;const int maxn=1e7+5;int T,R;int tot,pri[maxn],fac[maxn],inv[maxn];int ans[maxn];bool vis[maxn];void init(){	fac[0]=fac[1]=1;	inv[0]=inv[1]=1;	ans[0]=ans[1]=1;	for(int i=2;i<=maxn-5;i++)	{		fac[i]=(long long)(fac[i-1]*i)%R;//算阶乘		inv[i]=(long long)((R-R/i)*inv[R%i])%R;//逆元 	}	//欧拉筛 	for(int i=2;i<=maxn-5;i++)	{   	 	if(!vis[i])    	    pri[++tot]=i;  		for(int j=1;j<=tot&&i*pri[j]<=maxn-5;j++)   	    {       	    vis[i*pri[j]]=1;            if(i%pri[j]==0)                break;        }    }    for(int i=2;i<=maxn-5;i++)    {    	ans[i]=ans[i-1];    	if(!vis[i])    	ans[i]=(long long)(ans[i]*(i-1)%R*inv[i]%R)%R;	}}signed main(){	cin>>T>>R;	init();	while(T--)	{		int n,m;		cin>>n>>m;		cout<<(long long)fac[n]*ans[m]%R<<'\n';	}	return 0;}

CF632D Longest Subsequence

思路

我们要选出尽可能多的数字,满足他们的最小公倍数不大于m

分析题意可以得到,如果一个数大于m的话那么它和其他数的最小公倍数一定大于m

所以先把大于m的数字筛掉

然后

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>using namespace std;const int maxn=1000005;int n,m;int a[maxn],ans[maxn];int cnt[maxn];bool vis[maxn];int main(){	std::ios::sync_with_stdio(false); 	cin>>n>>m;	for(int i=1;i<=n;i++)	{		cin>>a[i];		if(a[i]<=m)		cnt[a[i]]++;	}	for(int i=1;i<=n;i++)	{		if(a[i]<=m&&!vis[a[i]])		{			vis[a[i]]=true;			for(int j=1;j*a[i]<=m;j++)			ans[a[i]*j]+=cnt[a[i]];		}	}	int maxx=-9999999;	int lcm=1;	for(int i=1;i<=m;i++)	{		if(maxx<ans[i])		{			maxx=ans[i];			lcm=i;		}	}	cout<<lcm<<" "<<maxx<<'\n';	for(int i=1;i<=n;i++)	{		if(lcm%a[i]==0)		cout<<i<<" ";	}	cout<<'\n';	return 0;}

CF582A GCD Table

思路

为啥CF的题目就是这么妙呢?

是我不配了

观察得到三个结论

1.G中最大的数也一定是a中最大的数,因为gcd(a,a)=agcd(a,a)=a,如果它是最大的话,也一定是最大的

2.G中次大的数也一定是a中次大的数

3.第三第四大的数可能是由最大和次大的GCD产生的;

所以算法就有这样的步骤

1.令p为G中最大的数,在G中删掉p,在a中加入p

2.对于a中的所有其他数,设为q,在G中删除两个gcd(p,q)gcd(p,q)

3.若G为空则结束,否则回到1

则时间复杂度为O(n2logGi,j)O(n^2logG_{i,j})

因为值域到10910^9,所以要用map处理

1
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#include <map>using namespace std;const int maxn=505;int n,cnt,tot;int arr[maxn],a[maxn*maxn];map <int,int> m;int gcd(int a,int b){	return b? gcd(b,a%b):a;}int main(){	cin>>n;	for(int i=1;i<=n*n;i++)	{		int x;		cin>>x;		if(!m[x])		a[++cnt]=x;		m[x]++;	}	//从小到大排序,方便从大到小遍历	sort(a+1,a+1+cnt);	for(int i=cnt;i>=1&&tot<n; )	{		//如果前面没有,就一直减 		while(!m[a[i]])		i--;		arr[++tot]=a[i];		m[a[i]]--;		for(int j=1;j<tot;j++)		m[gcd(a[i],arr[j])]-=2;		//它和其他数的gcd都删掉了,所以要减两遍	}	for(int i=1;i<=tot;i++)	cout<<arr[i]<<" ";	return 0; }