[Luogu P3768] 简单的数学题

本文详细解析了一道洛谷平台上的数学题目,该题目要求计算特定条件下的模运算结果。通过对题目的深入分析,文章提供了从数学角度理解并解决此类问题的方法,包括使用最大公约数、分块和前缀和等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门

题目描述

由于出题人懒得写背景了,题目还是简单一点好。

输入一个整数 n n n和一个整数 p p p,你需要求出 ( ∑ i = 1 n ∑ j = 1 n i j g c d ( i , j ) )   m o d   p (\sum_{i=1}^n\sum_{j=1}^n ijgcd(i,j))~mod~p (i=1nj=1nijgcd(i,j)) mod p,其中 g c d ( a , b ) gcd(a,b) gcd(a,b)表示 a a a b b b的最大公约数。

刚才题面打错了,已修改

输入输出格式

输入格式:

一行两个整数 p p p n n n

输出格式:

一行一个整数 ( ∑ i = 1 n ∑ j = 1 n i j g c d ( i , j ) )   m o d   p (\sum_{i=1}^n\sum_{j=1}^n ijgcd(i,j))~mod~p (i=1nj=1nijgcd(i,j)) mod p

输入输出样例

输入样例#1:
998244353 2000
输出样例#1:
883968974

说明

对于20%的数据, n ≤ 1000 n \leq 1000 n1000

对于30%的数据, n ≤ 5000 n \leq 5000 n5000

对于60%的数据, n ≤ 1 0 6 n \leq 10^6 n106,时限1s。

对于另外20%的数据, n ≤ 1 0 9 n \leq 10^9 n109,时限3s。

对于最后20%的数据, n ≤ 1 0 10 n \leq 10^{10} n1010,时限6s。

对于100%的数据, 5 × 1 0 8 ≤ p ≤ 1.1 × 1 0 9 5 \times 10^8 \leq p \leq 1.1 \times 10^9 5×108p1.1×109 p p p为质数。

解题分析

大力推式子:
∑ i = 1 n ∑ j = 1 n i j g c d ( i , j ) = ∑ d = 1 n d ∑ i = 1 n ∑ j = 1 n i j [ g c d ( i , j ) = d ] = ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ n d ⌋ i j d 2 [ g c d ( i , j ) = 1 ] = ∑ d = 1 n d 3 ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ n d ⌋ i j ∑ p ∣ g c d ( i , j ) μ ( p ) = ∑ d = 1 n d 3 ∑ p = 1 ⌊ n d ⌋ μ ( p ) p 2 ∑ i = 1 ⌊ n d p ⌋ ∑ j = 1 ⌊ n d p ⌋ i j \sum_{i=1}^n\sum_{j=1}^nijgcd(i,j) \\ =\sum_{d=1}^nd\sum_{i=1}^n\sum_{j=1}^nij[gcd(i,j)=d] \\ =\sum_{d=1}^nd\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ijd^2[gcd(i,j)=1] \\ =\sum_{d=1}^nd^3\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij\sum_{p|gcd(i,j)}\mu(p) \\ =\sum_{d=1}^nd^3\sum_{p=1}^{\lfloor\frac{n}{d}\rfloor}\mu(p)p^2\sum_{i=1}^{\lfloor\frac{n}{dp}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{dp}\rfloor}ij i=1nj=1nijgcd(i,j)=d=1ndi=1nj=1nij[gcd(i,j)=d]=d=1ndi=1dnj=1dnijd2[gcd(i,j)=1]=d=1nd3i=1dnj=1dnijpgcd(i,j)μ(p)=d=1nd3p=1dnμ(p)p2i=1dpnj=1dpnij
s ( n ) = ∑ i = 1 n i s(n)=\sum_{i=1}^ni s(n)=i=1ni, 并设 T = d p T=dp T=dp, 那么:
∑ d = 1 n d 3 ∑ p = 1 ⌊ n d ⌋ μ ( p ) p 2 ∑ i = 1 ⌊ n d p ⌋ ∑ j = 1 ⌊ n d p ⌋ i j = ∑ T = 1 n s ( n T ) 2 T 2 ∑ d ∣ T d μ ( T d ) \sum_{d=1}^nd^3\sum_{p=1}^{\lfloor\frac{n}{d}\rfloor}\mu(p)p^2\sum_{i=1}^{\lfloor\frac{n}{dp}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{dp}\rfloor}ij \\ =\sum_{T=1}^{n}s(\frac{n}{T})^2T^2\sum_{d|T}d\mu(\frac{T}{d}) d=1nd3p=1dnμ(p)p2i=1dpnj=1dpnij=T=1ns(Tn)2T2dTdμ(dT)
而我们知道 ϕ ( i ) = μ ( i ) ∗ i d ( i ) \phi(i)=\mu(i)*id(i) ϕ(i)=μ(i)id(i), 那么实际上这玩意就是:
∑ T = 1 n s ( n T ) 2 T 2 ϕ ( T ) \sum_{T=1}^{n}s(\frac{n}{T})^2T^2\phi(T) T=1ns(Tn)2T2ϕ(T)
前面可以下底分块, 关键在于后面的 f ( x ) = x 2 ϕ ( x ) f(x)=x^2\phi(x) f(x)=x2ϕ(x)上面, 这玩意该怎么科学筛出前缀和。

考虑到 ϕ ( i ) ∗ I = i d ( i ) \phi(i)*I=id(i) ϕ(i)I=id(i), 我们可以设 g ( x ) = x 2 g(x)=x^2 g(x)=x2, 那么
h ( x ) = g ( x ) ∗ f ( x ) = ∑ d ∣ x d 2 ∗ ϕ ( x d ) ( x d ) 2 = ∑ d ∣ x x 2 ϕ ( x ) = x 3 h(x)=g(x)*f(x) \\=\sum_{d|x}d^2*\phi(\frac{x}{d})(\frac{x}{d})^2 \\=\sum_{d|x}x^2\phi(x) \\ =x^3 h(x)=g(x)f(x)=dxd2ϕ(dx)(dx)2=dxx2ϕ(x)=x3
那么根据这个套路的式子:
g ( 1 ) S ( n ) = ∑ i = 1 n h ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) g(1)S(n)=\sum_{i=1}^nh(i)-\sum_{d=2}^{n}g(d)S(\lfloor\frac{n}{d}\rfloor) g(1)S(n)=i=1nh(i)d=2ng(d)S(dn)
∑ i = 1 n h ( i ) = ( x + 1 ) 2 x 2 4 , ∑ i = 1 n g ( i ) = n ∗ ( n + 1 ) ∗ ( 2 n + 1 ) 6 \sum_{i=1}^{n}h(i)=\frac{(x+1)^2x^2}{4},\sum_{i=1}^{n}g(i)=\frac{n*(n+1)*(2n+1)}{6} i=1nh(i)=4(x+1)2x2,i=1ng(i)=6n(n+1)(2n+1), 就可以科学筛出来了。

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <tr1/unordered_map>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 5005000
#define ll long long
int pcnt;
std::tr1::unordered_map <ll, ll> mp;
int phi[MX], pri[MX];
ll sum[MX], val[MX], MOD, inv2, inv4, inv6;
bool npr[MX];
void get()
{
	phi[1] = 1, val[1] = 1;
	R int i, j, tar;
	for (i = 2; i <= 5e6; ++i)
	{
		if (!npr[i]) pri[++pcnt] = i, phi[i] = i - 1;
		for (j = 1; j <= pcnt; ++j)
		{
			tar = i * pri[j];
			if (tar > 5e6) break;
			npr[tar] = true;
			if (!(i % pri[j])) {phi[tar] = phi[i] * pri[j]; break;}
			phi[tar] = phi[i] * phi[pri[j]];
		}
	}
	for (i = 1; i <= 5e6; ++i) sum[i] = (sum[i - 1] + 1ll * i * i % MOD * phi[i] % MOD) % MOD;
}
IN ll f1(ll val)
{return val % MOD * val % MOD * (val + 1) % MOD * (val + 1) % MOD * inv4 % MOD;}
IN ll f2(ll val)
{return val % MOD * (val + 1) % MOD * (2 * val % MOD + 1) % MOD * inv6 % MOD;}
IN ll fpow(ll base, ll tim)
{
	ll ret = 1;
	W (tim)
	{
		if (tim & 1)ret = ret * base % MOD;
		base = base * base % MOD, tim >>= 1;
	}
	return ret;
}
ll solve(R ll bd)
{
	if (bd <= 5e6) return sum[bd];
	if (mp.count(bd)) return mp[bd];
	ll ret = f1(bd);
	for (R ll lef = 2, rig; lef <= bd; lef = rig + 1)
	{
		rig = bd / (bd / lef);
		ret -= ((f2(rig) - f2(lef - 1) + MOD) % MOD) * solve(bd / lef) % MOD;
	}
	return mp[bd] = (ret + MOD) % MOD;
}
int main(void)
{
	ll n, ans = 0, bd, mul;
	scanf("%lld%lld", &MOD, &n);
	get();
	inv2 = fpow(2, MOD - 2);
	inv4 = fpow(4, MOD - 2);
	inv6 = fpow(6, MOD - 2);
	for (R ll lef = 1, rig; lef <= n; lef = rig + 1)
	{
		rig = n / (n / lef); bd = n / lef;
		mul = (1 + bd) % MOD * bd % MOD * inv2 % MOD;
		mul = mul * mul % MOD;
		(ans += mul * ((solve(rig) - solve(lef - 1) + MOD) % MOD) % MOD) %= MOD;
	}
	printf("%lld", ans);
}
<think>题目要求:给定一棵树,多次询问,每次询问给定三个点x,y,z,求一个点p,使得p到x,y,z的距离和最小,并输出该点和最小距离和。结论:1.集合点p一定是三个点两两的LCA(最近公共祖先)中的一个,且是深度最大的那个LCA。2.最小距离和=dep[x]+dep[y]+dep[z]-dep[lca(x,y)]-dep[lca(x,z)]-dep[lca(y,z)]因此,我们需要实现:-建树-预处理深度和倍增数组(用于求LCA)-对于每个询问,计算三个LCA:lca(x,y),lca(x,z),lca(y,z),然后取深度最大的那个作为p-然后按照上述公式计算距离和注意:距离和的计算公式推导:设p为所求点,则距离和为:dep[x]+dep[p]-2*dep[lca(x,p)]+同理y,z。但是这样不好直接计算。利用结论:点p是三个LCA中深度最大的那个,记为D。则距离和=(dep[x]+dep[y]+dep[z])-(dep[lca(x,y)]+dep[lca(x,z)]+dep[lca(y,z)])为什么?因为:考虑三个点两两的路径,它们会经过三个LCA,而整个路径被重复计算的部分就是这三个LCA的深度,且由于p是三个LCA中最深的,所以路径在p处汇聚。具体步骤:1.预处理:使用BFS或DFS求每个节点的深度和倍增数组(祖先数组fa[][])2.实现LCA函数:先调整深度,然后一起往上跳。3.处理每个询问。注意:本题数据范围n,m<=500000,因此使用倍增LCA时,注意数组大小和常数优化(使用scanf/printf)。代码实现如下:</think>### 问题解答 题目要求在一棵树中为三个点找到一个集合点,使得到这三个点的距离之和最小。以下是完整的C++解决方案: ```cpp #include <iostream> #include <cstdio> #include <vector> #include <queue> #include <algorithm> using namespace std; const int MAXN = 500005; const int LOG = 20; vector<int> graph[MAXN]; int depth[MAXN]; int parent[MAXN][LOG]; int n, m; void initLCA() { queue<int> q; q.push(1); depth[1] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : graph[u]) { if (v == parent[u][0]) continue; depth[v] = depth[u] + 1; parent[v][0] = u; for (int j = 1; j < LOG; j++) { parent[v][j] = parent[parent[v][j-1]][j-1]; } q.push(v); } } } int lca(int a, int b) { if (depth[a] < depth[b]) swap(a, b); for (int i = LOG-1; i >= 0; i--) { if (depth[parent[a][i]] >= depth[b]) { a = parent[a][i]; } } if (a == b) return a; for (int i = LOG-1; i >= 0; i--) { if (parent[a][i] != parent[b][i]) { a = parent[a][i]; b = parent[b][i]; } } return parent[a][0]; } int main() { scanf("%d %d", &n, &m); for (int i = 1; i < n; i++) { int u, v; scanf("%d %d", &u, &v); graph[u].push_back(v); graph[v].push_back(u); } initLCA(); while (m--) { int x, y, z; scanf("%d %d %d", &x, &y, &z); int l1 = lca(x, y); int l2 = lca(x, z); int l3 = lca(y, z); int p = l1; if (depth[l2] > depth[p]) p = l2; if (depth[l3] > depth[p]) p = l3; int dist = depth[x] + depth[y] + depth[z] - depth[l1] - depth[l2] - depth[l3]; printf("%d %d\n", p, dist); } return 0; } ``` ### 算法解释 1. **LCA预处理**: - 使用BFS遍历树并计算每个节点的深度 - 构建倍增数组`parent[][]`,其中`parent[i][j]`表示节点i的2^j级祖先 - 时间复杂度:O(n log n) 2. **LCA查询**: - 调整较深节点到相同深度 - 同时向上跳跃寻找最近公共祖先 - 时间复杂度:O(log n) 每次查询 3. **最优集合点选择**: - 计算三对点的LCA:`lca(x,y)`, `lca(x,z)`, `lca(y,z)` - 选择深度最大的LCA作为集合点p - 距离和公式:`dist = depth[x]+depth[y]+depth[z]-depth[l1]-depth[l2]-depth[l3]` 4. **数学证明**: - 三个点路径的交点必然在深度最大的LCA处 - 距离和公式本质是计算三条路径的总长度减去重叠部分 - 该点能最小化到三个点的路径总和 5. **复杂度分析**: - 预处理:O(n log n) - 每次查询:O(log n) - 总复杂度:O((n+m) log n),满足500000数据规模要求 ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值