ACwing算法提高课-第五章数学知识数论笔记

ACwing网址链接:活动 - AcWing

(文章中涉及的一些内容来自ACwing的算法提高课)

1.筛质数

(1)哥德巴赫猜想

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int prime[N],cnt;
bool st[N];
//st[i]==1为合数
//st[i]==0为素数
void init()
{
	st[1] = 1;
	for (int i = 2; i <= N; i++)
	{
		if (!st[i])prime[cnt++] = i;
		for (int j = 0; prime[j] * i <= N; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)break;
		}
	}
}
int main()
{
	init();
	int n;
	while (cin >> n && n)
	{
		int i = 1;
		while (i)
		{
			if (!st[n - prime[i]])
			{
				cout << n << " = " << prime[i] << " + " << n - prime[i] << endl;
				break;
			}
			i++;
		}
	}
	return 0;
}

(2)夏洛克和他的女朋友

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int prime[N], cnt;
bool st[N];
void init(int n)
{
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])prime[cnt++] = i;
		for (int j = 0; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)break;
		}
	}
}
int main()
{
	int n;
	cin >> n;
	init(n+1);//线性筛求素数,注意价值的范围,至少筛 n + 1 个 
	if (n > 2)
		cout << 2 << endl;
	else
		cout << 1 << endl;
	for (int i = 2; i <= n + 1; i++)
	{
		if (!st[i])cout << 1 << " ";
		else cout << 2 << " ";
	}
	cout << endl;
	return 0;
}

(3)筛质数

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 10;
int prime[N], cnt;
bool st[N];
//n是合数,则一定存在一个质数<=(根号n)
void init(int n)
{
	memset(st, 0, sizeof(st));
	cnt = 0;
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])prime[cnt++] = i;
		for (int j = 0; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)break;
		}
	}
}
int main()
{
	int l, r;
	while (cin >> l >> r)
	{
		init(5e4);
		//prime数组已存好
		memset(st, 0, sizeof(st));
		//把[l,r]区间内所有的合数用他们的最小质因子筛掉
		for (int i = 0; i < cnt; i++)
		{
			ll p = prime[i];
			for (ll j = max(2 * p, (l + p - 1) / p * p); j <= r; j += p)
			{
				st[j - l] = 1;
			}
		}
		//剩下的所有的都是素数
		cnt = 0;
		for (int i = 0; i <= r - l; i++)
		{
			if (!st[i] && i + l > 1)
				prime[cnt++] = i + l;
		}
		if(cnt<2)
			printf("There are no adjacent primes.\n");
		else
		{
			int minp = 0, maxp = 0;
			for (int i = 0; i + 1 < cnt; i++)
			{
				int d = prime[i + 1] - prime[i];
				if (d < prime[minp + 1] - prime[minp])minp = i;
				if (d > prime[maxp + 1] - prime[maxp])maxp = i;
			}
			printf("%d,%d are closest, %d,%d are most distant.\n",
				prime[minp], prime[minp + 1],
				prime[maxp], prime[maxp + 1]);
		}
	}
	return 0;
}

2.分解质因数(阶乘分解)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e7 + 10;
//ps:1~n有多少个q的倍数答案为:n/q
//若素数此时为2,则ans/2为1~ans中是2的倍数的个数
//若含有2^2=4,则还要算一次2,故j*=i,最后到j<=n
int prime[N];
int cnt;
bool st[N];
void init(int n)
{
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])prime[cnt++] = i;
		for (int j = 0; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)break;
		}
	}
}
int main()
{
	int n;
	cin >> n;
	init(n);
	ll ans = 0;
	for (int i = 2; i <= n; i++)
	{
		if (st[i])continue;
		ans = 0;
		for (ll j = i; j <= n; j *= i)//注意longlong!
		{
			ans += n / j;
		}
		if(ans)cout << i << " " << ans << endl;
	}
	return 0;
}

3.快速幂

(1)数列的第k个数

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 200907;
ll qmi(ll a, ll k)
{
	ll res = 1;
	while (k)
	{
		if (k & 1)res = res * a % mod;
		k >>= 1;
		a = a * a % mod;
	}
	return res;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		ll a, b, c;
		cin >> a >> b >> c;
		ll k;
		cin >> k;
		if (b - a == c - b)
		{
			ll d = b - a;
			cout << (a + (k - 1) * d % mod) % mod << endl;
		}
		else
		{
			ll d = b / a;
			cout << a * qmi(d, k - 1) % mod << endl;
		}
	}
	return 0;
}

(2)越狱(输出不能为负数取模)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 100003;
ll qmi(ll a, ll k)
{
	ll res = 1;
	while (k)
	{
		if (k & 1)res = res * a % mod;
		k >>= 1;
		a = a * a % mod;
	}
	return res;
}
int main()
{
	ll m, n;
	cin >> m >> n;
	//为了避免最终结果res为负数
	//(res%mod+mod)%mod----->输出
	cout << (qmi(m, n) % mod - m* qmi(m - 1, n - 1) %mod + mod)%mod << endl;
	return 0;
}

4.约数个数

(1)轻拍牛头

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
const int M = 1e6 + 10;
int a[N];
int cnt[M];
int res[M];
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		cnt[a[i]]++;
	}
	//枚举可能出现的ai(从小到大!)
	for (int i = 1; i <= M; i++)
	{
		if (!cnt[i])continue;
		for (int j = i; j <= M; j += i)
		{
			res[j] += cnt[i];
		}
	}
	for (int i = 1; i <= n; i++)
		cout << res[a[i]] - 1 << endl;
	return 0;
}

(2)樱花(推公式约数之和)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
//求(n!)的平方的正约数个数和
//求n!的正约数个数和*2
int n;
int prime[N], cnt;
int c[N];//c[i]为指数
int s[N];//s[i]表示i的最小质因子在prime数组中的位置
bool st[N];
void get_prime()
{
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])
		{
			prime[++cnt] = i;
			s[i] = cnt;
		}
		for (int j = 1; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			s[prime[j] * i] = j;
			if (i % prime[j]==0)break;
		}
	}
}
void divide(int x)
{
	while (x != 1)
	{
		c[s[x]]++;
		x /= prime[s[x]];
		//x分解
	}
}
int main()
{
	cin >> n;
	//先筛质数
	get_prime();
    //由于求阶乘,将1~n分解质因数
	for (int i = 1; i <= n; i++)
	{
		divide(i);
	}
	int ans = 1;
	for (int i = 1; i <= N; i++)
	{
		if (c[i])
			ans = (ll)ans * (2 * c[i] + 1) % mod;
	}
	cout << ans << endl;
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
//求(n!)的平方的正约数个数和
//求n!的正约数个数和*2
int n;
int prime[N], cnt;
bool st[N];
void get_prime()
{
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])
		{
			prime[++cnt] = i;
		}
		for (int j = 1; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j]==0)break;
		}
	}
}
int main()
{
	cin >> n;
	//先筛质数
	get_prime();
	int ans = 1;
	for (int i = 2; i <= n; i++)
	{
		if (st[i])continue;
		ll res = 0;
		for (ll j = i; j <= n; j *= i)res += n / j;
		ans = (ll)ans*(res * 2 + 1) % mod;
	}
	cout << ans << endl;
	return 0;
}

5.欧拉函数

(1)可见的点(互质对)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e3 + 10;
int prime[N],cnt;
bool st[N];
int phi[N];
//若要统计一个数N在1~N中与N互质的个数可以用欧拉函数
//可见点的数量等于1到N中每个点i在1~i与i互质的个数之和
//因为是对称的,只需要计算下半部分,斜率y/x小于1,即x要大于y即找与x互质的y一定要比x小
void init()//N<=1000,先进行统计,phi[i]为i与1到N中互质的个数
{
	phi[1] = 1;
	st[1] = 1;//不是质数
	for (int i = 2; i <= N; i++)
	{
		if (!st[i])
		{
			prime[cnt++] = i;
			phi[i] = i - 1;
			//phi[i]=i*(1-1/i)
		}
		for (int j = 0; prime[j] * i <= N; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)
			{
				phi[prime[j] * i] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[prime[j] * i] = phi[i] * (prime[j] - 1);
			}
		}
	}
}
int main()
{
	int t;
	cin >> t;
	init();
	for (int i = 1; i <= t; i++)
	{
		int n;
		cin >> n;
		int res = 0;//1,1点
		for (int j = 1; j <= n; j++)
		{
			res += phi[j];
		}
		res *= 2;
		res += 1;//1,1点
		cout <<i<<" "<<n<<" "<< res << endl;
	}
	return 0;
}

(2)最大公约数是素数的对数

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e7 + 10;
int prime[N],cnt;
bool st[N];
int phi[N];
ll s[N];
//求gcd(x.y)为素数的(x,y)对数
//gcd(x,y)=p(于0~N) --->
//gcd(x/p,y/p)=1(于0~N/p) --->
//求x1,y1(于0~N/p)互为质数的对数
//思路:先求出1~n中的素数prime,并求欧拉phi(线性筛)
// 由于是n/p故最后的总和为s[n/p]而不是全部phi相加的s[n]
// 由于n/p是随p变的,所以我们需要用前缀和记录
//接着枚举每一个素数p,res+=s[n/p]*2+1;对于每个p,有每组x1和y1,而每组x1和y1于0~n/p也有一定数量的互质对
//乘2是因为欧拉函数只统计斜率为1的下半部分,设斜率为y/x,即x>y
//而y>x也可以,且关于斜率为1对称,加一的(1,1)点
void init(int n)
{
	phi[1] = 0;
	st[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!st[i])
		{
			prime[cnt++] = i;
			phi[i] = i - 1;
		}
		for (int j = 0; prime[j] * i <= n; j++)
		{
			st[prime[j] * i] = 1;
			if (i % prime[j] == 0)
			{
				phi[prime[j] * i] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[prime[j] * i] = phi[i] * (prime[j] - 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		s[i] = s[i - 1] + phi[i];
	}
}
int main()
{
	int n;
	cin >> n;
	init(n);
	ll res = 0;
	for (int i = 0; i < cnt; i++)
	{
		int p = prime[i];
		res += 2 * s[n/p]+1;
		//注意到若res += 2 * s[i],且phi[1]=1,那么(1,1)这个点会被算2次
		//所以res+=(2*s[i]+1),且phi[1]=0,这样就不会重复了
	}
	cout << res << endl;
	return 0;
}

6.矩阵乘法

(1)斐波那契前 n 项和

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3;
int n, m;
//求fn的前n项和Sn%m(其中fn为斐波那契数列)
//F[n]=[f[n],f[n+1]],F[n+1]=[f[n+1],f[n+2]]
//[f[n],f[n+1]][0 1]=[f[n+1],f[n+2]]
//             [1 1](设为A)
// F[n]=F[b]*(A^n)
//[f[n],f[n+1],s[n]][0 1 0]=[f[n+1],f[n+2],s[n+1]]
//                  [1 1 1]
//                  [0 0 1](设为A)
//F[n]*A=F[n+1],F[n]=F[1]*(A^(n-1))
//答案即为F[2]项
void mul(int c[], int a[], int b[][N])
{
	int temp[N] = { 0 };
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			temp[i] =( temp[i] +(ll)a[j] * b[j][i])%m;
		}
	}
	memcpy(c, temp, sizeof(temp));
}
void mul(int c[][N], int a[][N], int b[][N])
{
	int temp[N][N] = { 0 };
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			for (int k = 0; k < N; k++)
			{
				temp[i][j] = (temp[i][j] + (ll)a[i][k] * b[k][j]) % m;
			}
		}
	}
	memcpy(c, temp, sizeof(temp));
}
int main()
{
	cin >> n >> m;
	int a[N][N] = {
		{0,1,0},
		{1,1,1},
		{0,0,1},
	};
	int f1[N] = { 1,1,1 };
	//f1[3]={f0,f1,s0}
	n--;
	//矩阵快速幂(a^k---->a^n)
	while (n)
	{
		if (n & 1)
			mul(f1, f1, a);//res=res*a;
		mul(a, a, a);//a=a*a;
		n >>= 1;
	}
	cout << f1[2] << endl;
	return 0;
}

 (2)佳佳的斐波那契

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 4;
ll n, m;
ll a[N][N] = {
		{0,1,1,0},
		{1,1,1,0},
		{0,0,1,1},
		{0,0,0,1},
};
ll X[N] = { 0,1,1,0 };
//X[4]={f0,f1,s1,g0}
void mul(ll c[], ll a[], ll b[][N])
{
	ll temp[N] = { 0 };
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			temp[i] =( temp[i] +(ll)a[j] * b[j][i])%m;
		}
	}
	memcpy(c, temp, sizeof(temp));
}
void mul(ll c[][N], ll a[][N], ll b[][N])
{
	ll temp[N][N] = { 0 };
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			for (int k = 0; k < N; k++)
			{
				temp[i][j] = (temp[i][j] + (ll)a[i][k] * b[k][j]) % m;
			}
		}
	}
	memcpy(c, temp, sizeof(temp));
}
void qmi(int n)
{
	while (n)
	{
		if (n & 1)mul(X,X, a);//X=X*a;
		mul(a,a, a);//a=a*a;
		n >>= 1;
	}
}
int main()
{
	cin >> n >> m;
	//Xn=X1*(A^(n-1))=Xn-1*A
	//Tn=n*Sn-(Gn-1)
	qmi(n - 1);
	printf("%lld\n", ((n * X[2] - X[3]) % m + m) % m);
	//防止负数输出
	return 0;
}

7.组合计数

(1)牡牛和牝牛

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
const int mod = 5000011;
int f[N];
//f[i]表示i头牛排成一排的方案数
//(1)1<=i<=k+1,f[i]=i+1;最多只能放一头D,若放一头则有i个位置可以选或者一头也不放
//(2)i>k+1,考虑最后一头是不是D,若不是则前面i-1头可任意排则方案为f[i-1]
//若是,则前面第i-k到i-1都不能是D,方案为f[i-k-1]
//即f[i]=f[i-1]+f[i-k-1]
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 1; i <= k + 1; i++)
		f[i] = i + 1;
	for (int i = k + 2; i <= n; i++)
	{
		f[i] = (f[i - 1] + f[i - k - 1])%mod;
	}
	cout << f[n] << endl;
	return 0;
}

(2)方程的解

#include<bits/stdc++.h>
using namespace std;
#define ll long long
//a1+a2+...+ak=x^x%1000
//已知k和x,求有多少组a使其成立
const int N = 150, mod = 1000;
int k, x;
//n==x^x
int f[1000][100][N];
//f[i][j]==C[i][j]-->C[n-1][k-1]答案
int qmi(int a, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1)res = (ll)res * a % mod;
		a = (ll)a * a % mod;
		k >>= 1;
	}
	return res;
}
//高精度a=b+c
void add(int a[N], int b[N], int c[N])
{
	for (int i = 0, t = 0; i < N; i++)
	{
		t += b[i] + c[i];
		a[i] = t % 10;
		t /= 10;
	}
}
int main()
{
	cin >> k >> x;
	//n=x^x;
	int n = qmi(x % mod, x);
	//C(n-1,k-1)
	//C(i,j)=C(i-1,j-1)+C(i-1,j)
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= i && j < k; j++)
		{
			if (j == 0)f[i][j][0] = 1;
			else add(f[i][j], f[i - 1][j], f[i - 1][j - 1]);
		}
	}
	//输出答案
	int tt = N - 1;
	while (!f[n - 1][k - 1][tt])tt--;
	while (tt >= 0)
		cout << f[n - 1][k - 1][tt--];
	return 0;
}

(3)车的放置

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 100003;
const int N = 2010;
//(N开两倍是因为出现了a+c)
// C(a,b)=a!/(b!*(a-b)!)=f[a]*inf[b]*inf[a-b]
// A(a,b)=a!/(a-b)!=f[a]*inf[a-b]
//答案C(b,i)*A(a,i)*C(d,k-i)*A(a+c-i,k-i)
int a, b, c, d, k;
int f[N], inf[N];
int qmi(int a, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1)res =(ll)res * a % mod;
		k >>= 1;
		a = (ll)a * a % mod;
	}
	return res;
}
int C(int a, int b)
{
	if (a < b)return 0;
	return (ll)f[a] * inf[b] % mod * inf[a - b] % mod;
}
int A(int a, int b)
{
	if (a < b)return 0;
	return (ll)f[a] * inf[a - b] % mod;
}
int main()
{
	cin >> a >> b >> c >> d >> k;
	f[0] = inf[0] = 1;
	for (int i = 1; i < N; i++)
	{
		f[i] = (ll)f[i - 1] * i % mod;
		inf[i] = (ll)inf[i - 1] * qmi(i, mod - 2) % mod;//求逆元!
	}
	int res = 0;
	for (int i = 0; i <= b; i++)
	{
		res = (res + (ll)C(b, i) * A(a, i) % mod * C(d, k - i) % mod * A(a + c - i, k - i) % mod)%mod;
	}
	//防止输出负数
	cout << (res % mod + mod) % mod << endl;
	return 0;
}

 (4)数字三角形

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000 + 10;
//给n*m的网格,计算三角形个数
int n, m;
ll ans = 0;
//总数C(n*m,3)-不合法
//(1)水平n*C(m,3)---->n,m格点数
//(2)竖直m*C(n,3)---->n,m格点数
// ---->n,m为边长
//(3)斜线(斜率大于0):底为j,高为i的直角三角形,共有(n-i+1)*(m-j+1)种三角形
//其中一个三角形的斜边对三点贡献即为(gcd(i,j)-1)种
//共有(包括斜率小于0)2*(n-i+1)*(m-j+1)*(gcd(i,j)-1)种
//答案为C(n*m,3)-2*(n-i+1)*(m-j+1)*(gcd(i,j)-1)
int gcd(int a, int b)
{
	return b ? gcd(b,a%b):a;
}
ll C(ll a, ll b)
{
	return a * (a - 1) * (a - 2) / 6;
}
int main()
{
	cin >> n >> m;
	//转换为格点数
	n++, m++;
	ll res = C(n * m, 3) - n * C(m, 3) - m * C(n, 3);
	//转换成长度
	n--, m--;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			res -= 2 * (n - i + 1) * (m - j + 1) * (gcd(i, j) - 1);
		}
	}
	cout << res << endl;
	return 0;
}

(5)序列统计

 

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e6 + 3;
//序列统计:统计长度在1~N之间,元素大小都在L到R之间的
//单调不降序列的数量。设序列长度为k
//[l,r]---->[0,r-l]映射
//0<=a1<=a2<=...<=ak<=r-l,其中ai是[0,r-l]的序列个数
//令x1=a1,x2=a2-a1,..,xk=ak-(ak-1),即0<=x1+x2+..+xk<=r-l
//求满足条件的{xk}数列的个数-->用不超过r-l的小球放入k个盒子,盒子可为空的方案数
int qmi(int a, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1)res = (ll)res * a % mod;
		a = (ll)a * a % mod;
		k >>= 1;
	}
	return res;
}
int C(int a, int b)
{
	int res = 1, inv = 1;
	for (int i = 1, j = a; i <= b; i++, j--)
	{
		res = (ll)res * j % mod;
		inv = (ll)inv * i % mod;
	}
	res = (ll)res * qmi(inv, mod - 2) % mod;
	return res;
}
int lucas(ll a, ll b)
{
	if (a < mod && b < mod)return C(a, b);
	return (ll)C(a%mod,b%mod)*lucas(a/mod,b/mod)%mod;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		ll n, l, r;
		cin >> n >> l >> r;
		int res = lucas(r - l + n + 1, r - l + 1) - 1;
		cout << (res % mod + mod) % mod << endl;
	}
	return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值