数论做题笔记

(1) P6075 [JSOI2015] 子集选取

  • 给定 n 个元素的集合 S={1,2,,n} 和整数 k。求有多少组 12k(k+1) 个集合 Ai,j 使得 Ai,jS,1jikAi,jAi,j1Ai1,j
  • n,k109

首先考虑 n=1。那么一定存在某条轮廓线,使得这条轮廓线上的集合均为 {1},轮廓线下的集合均为

那么答案显然为轮廓线的数量,也即从左下角往右上走,且只能向上或向右,最终到达图中绿点的方案数。总步数为 k,那么方案数即 2k

考虑更普遍的 n>1 的情况。可以发现对于不同的数字是可以分别考虑的,也就是说每个格点中是否包含某个数 x 的情况所构成的图也是存在这一条轮廓线的。例如 n=3

那么答案即为 (2k)n=2kn

Code
int fpm(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P;
	}
	return res;
}

void Luogu_UID_748509() {
	int a, b;
	fin >> a >> b;
	fout << fpm(2, a * b) << '\n';
}

(2) P1072 [NOIP2009 提高组] Hankson 的趣味题

  • n 组询问。每次给定 a0,a1,b0,b1,求有多少正整数 x 使得 gcd(a0,x)=a1lcm(b0,x)=b1
  • 1a0,a1,b0,b12×109n2×103

对于一个质数 i,我们令 fx(i) 表示 x 分解质因数后 i 的出现次数。

那么我们可以对于 x 的每个质因数单独考虑,求出每个质因数的取值方案数,那么所有这些方案数相乘即为最终答案。不妨令我们当前考虑的质数为 i

通过 gcd(a0,x)=a1 可以得知 min(fa0(i),fx(i))=fa1(i),通过 lcm(b0,x)=b1 可以得知 max(fb0(i),fx(i))=fb1(i)。其中 fa0(i),fa1(i),fb0(i),fb1(i) 都是可以预处理求知的。

此时发现通过这两个条件可以推出两个 fx(i) 的取值范围,这两个范围再取交集的长度即这个质因数的方案数。

Code
int a0, a1, b0, b1;
map<int, int> primes[4];
set<int> S;

void init(int x, int id) {
	for (int i = 2; i <= x / i; ++ i )
		if (x % i == 0) {
			S.insert(i);
			while (x % i == 0) {
				++ primes[id][i];
				x /= i;
			}
		}
	
	if (x > 1) {
		S.insert(x);
		++ primes[id][x];
	}
}

struct Range {
	int l, r;
	Range operator +(const Range &h) const {
		return {max(l, h.l), min(r, h.r)};
	}
};

Range calcmin(int a, int b) {
	// min(a, x) == b,x 的范围?
	if (a < b) return {INF, -INF};
	if (a == b) return {a, INF};
	return {b, b};
}

Range calcmax(int a, int b) {
	// max(a, x) == b,x 的范围?
	if (a > b) return {INF, -INF};
	if (a == b) return {-INF, a};
	return {b, b};
}

void Luogu_UID_748509() {
	S.clear();
	for (int i : {0, 1, 2, 3}) primes[i].clear();
	
	fin >> a0 >> a1 >> b0 >> b1;
	init(a0, 0), init(a1, 1), init(b0, 2), init(b1, 3);
	
	int res = 1;
	for (int p : S) {
		auto range = calcmin(primes[0][p], primes[1][p]) + calcmax(primes[2][p], primes[3][p]);
		res *= max(0ll, range.r - range.l + 1);
	}
	
	fout << res << '\n';
	return;
}

(3) P4139 上帝与集合的正确用法

  • T 组询问,给定 p,求 2222modp
  • T103p107

x=2222,f(p)=222modp。不难发现 x=2x,所求为 f(p)

根据扩展欧拉定理可得:

2x{2xx<φ(p)2xmodφ(p)+φ(p)xφ(p)(modp)

很显然 x 特别大,所以:

2x2xmodφ(p)+φ(p)(modp)

即:

f(p)=2f(φ(p))+φ(p)

可以发现 φ(p)<p,所以上式暴力递归的复杂度是与 p 同阶的。

边界显然 f(1)=0

Code
int fpm(int a, int b, int P) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P;
	}
	return res;
}

int p[N], cnt, phi[N];
bool st[N];

void init(int n) {
	phi[1] = 1;
	for (int i = 2; i <= n; ++ i ) {
		if (!st[i]) phi[i] = i - 1, p[ ++ cnt] = i;
		for (int j = 1; p[j] <= n / i; ++ j ) {
			st[p[j] * i] = true;
			if (i % p[j] == 0) {
				// p[j] 是 i 的最小质因子
				phi[p[j] * i] = phi[i] * p[j];
				break;
			}
			phi[p[j] * i] = phi[i] * (p[j] - 1);
		}
	}
}

int solve(int p) {
	if (p == 1) return 0;
	return fpm(2, solve(phi[p]) + phi[p], p);
}

void Luogu_UID_748509() {
	int x;
	fin >> x;
	fout << solve(x) << '\n';
}

(4) P1313 [NOIP2011 提高组] 计算系数

  • 给定一个多项式 (ax+by)k,请求出多项式展开后 xn×ym 项的系数。
  • k103n+m=ka,b106

首先视 x0=ax,y0=by,那么原式可以用二项式定理展开:

(x0+y0)k=i=0k(ki)×x0i×y0ki

显然 x0n×y0m 的系数为 (kn)

因为 x0n=(ax)n=anxn,y0m=(by)m=bmym,所以 xn×ym 的系数为 (kn)×an×bm

Code
int a, b, k, n, m;

int fpm(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P;
	}
	return res;
}

int C(int a, int b) {
	int res = 1;
	for (int i = a - b + 1; i <= a; ++ i ) res = res * i % P;
	for (int i = 1; i <= b; ++ i ) res = res * fpm(i, P - 2) % P;
	return res;
}

void Luogu_UID_748509() {
	fin >> a >> b >> k >> n >> m;
	fout << C(k, n) * fpm(a, n) % P * fpm(b, m) % P << '\n';
}

(5) 51Nod - 1642 区间欧拉函数

  • 给定 n 个数 aiq 次查询 φ(i=lrai)mod(109+7)
  • n,q2×105ai106

对于 φ(n) 的求解,首先将 n 质因数分解为 pici,那么 φ(n)=n×(11pi)。也就是说求解 φ(n) 只需要知晓 n 的质因子,而并不需要知晓 n 的质因子的出现次数。

同理,对于 φ(ai) 的求解,我们需要求出 ai 的质因数,也即每个 ai 的质因数的并。

回到题目。仿照 HH 的项链 的思路,我们可以将 q 次询问离线并按照右端点排序。将每个质数的权值定义为 11p,然后用线段树求解区间权值乘积,基本就解决了。

Code
int n, q, a[N];

struct Query {
	int l, r, id;
	bool operator <(const Query &h) const {
		return r == h.r ? l < h.l : r < h.r;
	}
}que[N];

int res[N];

int fpm(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		b >>= 1, a = (ll)a * a % P;
	}
	return res;
}

int calc(int p) {
	return (ll)(p - 1) * fpm(p, P - 2) % P;
}

struct Segment_Tree {
	struct Node {
		int l, r, v;
	}tr[N << 2];
	
	void pushup(int u) {
		tr[u].v = (ll)tr[u << 1].v * tr[u << 1 | 1].v % P;
	}
	
	void build(int u, int l, int r, bool op) {
		tr[u] = {l, r};
		if (l == r) tr[u].v = op ? 1ll : a[l] % P;
		else {
			int mid = l + r >> 1;
			build(u << 1, l, mid, op), build(u << 1 | 1, mid + 1, r, op);
			pushup(u);
		}
	}
	
	void modify(int u, int x, int d) {
		if (tr[u].l == tr[u].r) tr[u].v = d;
		else {
			int mid = tr[u].l + tr[u].r >> 1;
			if (x <= mid) modify(u << 1, x, d);
			else modify(u << 1 | 1, x, d);
			pushup(u);
		}
	}
	
	int query(int u, int l, int r) {
		if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
		int mid = tr[u].l + tr[u].r >> 1, res = 1;
		if (l <= mid) res = query(u << 1, l, r);
		if (r > mid) res = (ll)res * query(u << 1 | 1, l, r) % P;
		return res;
	}
	
	void modify(int x, int d) {
		int t = query(1, x, x);
		if (d > 0) modify(1, x, (ll)t * d % P);
		else modify(1, x, (ll)t * fpm(-d, P - 2) % P);
	}
	
	int query(int l, int r) {
		return query(1, l, r);
	}
}A, B;

vector<int> primes(int x) {
	vector<int> res;
	for (int j = 2; j <= x / j; ++ j )
		if (x % j == 0) {
			res.push_back(j);
			while (x % j == 0) x /= j;
		}
	if (x > 1) res.push_back(x);
	return res;
}

int lst[M];

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n; ++ i ) fin >> a[i];
	
	A.build(1, 1, n, 0), B.build(1, 1, n, 1);
	
	fin >> q;
	for (int i = 1; i <= q; ++ i ) {
		fin >> que[i].l >> que[i].r;
		que[i].id = i;
		res[i] = A.query(que[i].l, que[i].r);
	}
	sort(que + 1, que + q + 1);
	
	for (int i = 1, j = 1; i <= q; ++ i ) {
		for (; j <= que[i].r; ++ j ) {
			auto v = primes(a[j]);
			for (int p : v) {
				if (lst[p]) B.modify(lst[p], -calc(p));
				lst[p] = j;
				B.modify(j, calc(p));
			}
		}
		
		res[que[i].id] = (ll)res[que[i].id] * B.query(que[i].l, que[i].r) % P;
	}
	
	for (int i = 1; i <= q; ++ i ) fout << res[i] << '\n';
	return;
}

(6) CF616E Sum of Remainders

  • 给定 n,m,求 (i=1mnmodi)mod(109+7)
  • n,m1013

首先根据取模的定义可知 amodb=aab×b,那么:

i=1mnmodi=i=1m(nni×i)=n×mi=1m(ni×i)

然后发现 i=1m(ni×i) 是一个整除分块的形式。然后做完了。

注意当 i>n 时是无贡献的,所以只需要枚举到 min(n,m) 即可。

Code
int n, m, res;

int mod(int x) {
	return x % P;
}

int calc(int l, int r) {
	return mod(l + r) * mod(r - l + 1) % P * 500000004ll % P;
}

void Luogu_UID_748509() {
	fin >> n >> m;
	int res = mod(n) * mod(m) % P;
	for (int l = 1, r; l <= min(n, m); l = r + 1) {
		r = min(n / (n / l), m);
		// cout << l << ' ' << r << ' ' << (n / l) % P << ' ' << (r - l + 1) % P << '\n';
		res = ((res - mod(n / l) * calc(l, r) % P) % P + P) % P;
	}
	fout << res;
}

(7) P2398 GCD SUM

  • i=1nj=1ngcd(i,j)
  • n105

推式子。

i=1nj=1ngcd(i,j)=i=1nj=1ndgcd(i,j)φ(d)=i=1nj=1ndidjφ(d)=d=1nφ(d)nd2

线性筛 φ 即可。

Code
int p[N];		// p[i] 表示第 i 个质数
bool st[N];		// st[i] 表示 i 是否是质数
int phi[N];		// phi[i] 表示 φ(i) 的值
int cnt;

void prime(int n)
{
	phi[1] = 1;
	for (int i = 2; i <= n; i ++ )
	{
		if (!phi[i]) p[ ++ cnt] = i, phi[i] = i - 1;
		for (int j = 1; j <= cnt && p[j] <= n / i; j ++ )
		{
			if (i % p[j]) phi[i * p[j]] = phi[i] * phi[p[j]];		// p[j] 不是 i 的最小质因子 
			else 	// p[j] 是 i 的最小质因子 
			{
				phi[i * p[j]] = phi[i] * (p[j]);
				break;
			}
		}
	}
}

void Luogu_UID_748509() {
	int n; fin >> n;
	prime(n);
	int res = 0;
	for (int i = 1; i <= n; ++ i ) res += phi[i] * (n / i) * (n / i);
	fout << res;
}
posted @   2huk  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示