【洛谷题解/SDOI2012】P2303/SDOI2012 D1T1 Longge的问题

本文详细解析洛谷P2303题目,介绍如何通过质因数分解和欧拉函数解决∑i=1ngcd(i,n)的问题,并给出AC代码实现。

-放弃不难,但坚持很酷
-欢迎诸君感受思维与数学的魅力

原题链接:https://www.luogu.com.cn/problem/P2303
难度:提高+/省选-(省选D1T1)
涉及知识点:质因数分解、欧拉函数、思维

题意

求解 ∑ i = 1 n gcd ⁡ ( i , n ) , n ≤ 2 32 \sum\limits_{i=1}^{n}\gcd(i,n),n\leq2^{32} i=1ngcd(i,n),n232,简明的。

分析与解决

这是一道非常经典的推式子题,我们需要对原式进行一些推导和分析。笔者认为,这道题是很锻炼思维的。

由于笔者也是学习的粉兔的题解,所以本题解将完全复述一遍粉兔的思路,但会加以解释。

对于原式,我们将其转化为:
∑ j = 1 n ( j × ∑ i = 1 n [ gcd ⁡ ( i , n ) = j ] ) \sum\limits_{j=1}^{n}(j\times \sum\limits_{i=1}^{n}[\gcd(i,n)=j]) j=1n(j×i=1n[gcd(i,n)=j])
上式的意义为累加一个最大公约数值 j j j 1 ∼ n 1\sim n 1n 中该数值出现的次数的乘积。然后,将上式变换为:
∑ j = 1 n ( j × ∑ i = 1 n [ gcd ⁡ ( i / j , n / j ) = 1 ] ( j ∣ i , j ∣ n ) ) \sum\limits_{j=1}^{n}(j\times \sum\limits_{i=1}^{n}[\gcd(i/j,n/j)=1](j|i,j|n)) j=1n(j×i=1n[gcd(i/j,n/j)=1](ji,jn))
上式的意义是进行了一次数学上的变换,然后我们发先 j j j 乘的个数实际上就是 i / j i/j i/j n / j n/j n/j 互质的数的个数,所以我们可以改写为欧拉函数:
∑ j = 1 n ( j × φ ( n / j ) ( j ∣ n ) ) = ∑ j ∣ n ( j × φ ( n / j ) ) ① \sum\limits_{j=1}^{n}(j\times \varphi(n/j)(j|n))\\ =\sum\limits_{j|n}(j\times \varphi(n/j))① j=1n(j×φ(n/j)(jn))=jn(j×φ(n/j))
大多数的题解到这一步也就结束了,实际上到这一部分也就可以解题了,令因数个数为 k k k,复杂度为 O ( k n ) O(k\sqrt {n}) O(kn ),可以接受。

但是我们还可以对上式进行化简,我们设一个质数 p p p 表示 n n n 的质因子。 继续对 ① ① 式进行变换:
∑ j ∣ n ( n / j × φ ( j ) ) \sum\limits_{j|n}(n/j\times \varphi(j)) jn(n/j×φ(j))
然后展开为欧拉函数的公式:
∑ j ∣ n ( n / j × ( j × ∏ p ∣ j p − 1 p ) ) \sum\limits_{j|n}(n/j\times(j\times\prod\limits_{p|j} \frac{p-1}{p})) jn(n/j×(j×pjpp1))
j j j 和它的逆元抵消掉,得:
∑ j ∣ n ( n × ∏ p ∣ j p − 1 p ) = n × ∑ j ∣ n ∏ p ∣ j p − 1 p \sum\limits_{j|n}(n\times \prod\limits_{p|j}\frac{p-1}{p})\\ =n\times \sum\limits_{j|n}\prod_{p|j}\frac{p-1}{p} jn(n×pjpp1)=n×jnpjpp1

然后运用唯一分解定理,令 n = p 1 b 1 p 2 b 2 p 3 b 3 . . p k b k . n=p_1^{b_1}p_2^{b_2}p_3^{b_3}..p_k^{b_k}. n=p1b1p2b2p3b3..pkbk.,并定义 f i = p i − 1 p i \large f_i=\frac{p_i-1}{p_i} fi=pipi1

那么对于 n n n 的一个因子 j j j,可以将其表示为 j = p 1 c 1 p 2 c 2 p 3 c 3 . . . p k c k ( 0 ≤ c i ≤ b i ) j=p_1^{c_1}p_2^{c_2}p_3^{c_3}...p_k^{c_k}(0\leq c_i\leq b_i) j=p1c1p2c2p3c3...pkck(0cibi),那么 φ ( j ) = ∏ p ∣ j p − 1 p = ∏ i = 1 k f i ( c i > 0 ) \varphi(j)=\prod\limits_{p|j}\frac{p-1}{p}=\prod\limits_{i=1}^{k}f_i(c_i>0) φ(j)=pjpp1=i=1kfi(ci>0)。为什么要保证 c i > 0 c_i>0 ci>0,原因是显然的,如果 c i = 0 c_i=0 ci=0 j j j 的某个质因子不存在。

我们观察一类 ∏ i = 1 k f i ( c i > 0 ) \prod\limits_{i=1}^{k}f_i(c_i>0) i=1kfi(ci>0) 相等的因子 j , j ′ , j ′ ′ , . . . j,j',j'',... j,j,j′′,...,它们必须满足在 i i i 相等的情况下, c i c_i ci 同时大于或同时等于 0。因为如果前式相等,则说明这类数的质因子集合是相同的,只是可能 c i c_i ci ,即 p i p_i pi 的个数不同。基于该结论,我们可以推导出在这类数中,对于一个 p i p_i pi,要么同时出现,要么同时没有,也就是所谓的 c i c_i ci 同时大于或者同时等于 0。

那么这一类数有多少个呢?如果对于属于这类数的 j j j 有质因子 p q 1 , p q 2 , p q 3 , . . . p q g p_{q_1},p_{q_2},p_{q_3},...p_{q_g} pq1,pq2,pq3,...pqg,那么显然这类数 j j j 的答案为 ∏ i = 1 g f q i \prod\limits_{i=1}^{g}f_{q_i} i=1gfqi。令 b i b_i bi 为质因子 p i p_i pi 的指数,可以得到 j j j 的质因子个数为 ∏ i = 1 g b q i \prod\limits_{i=1}^{g}b_{q_i} i=1gbqi。那么就可以得到 j j j 对答案的贡献为 ∏ i = 1 g X q i \prod\limits_{i=1}^{g}X_{q_i} i=1gXqi X i = f i × b i X_i=f_i\times b_i Xi=fi×bi,也就是某一个 j j j 的某一个质因子的贡献乘上它的个数。

如果你能坚持看到这里,你一定不难发现每一个质因子的贡献都是独立的,那么最后我们枚举 n n n 的每一个质因子使用或者不适用,得到最终的答案 a n s ans ans 为:
a n s = n × ∏ i = 1 k ( X i + 1 ) ans=n\times \prod\limits_{i=1}^{k}(X_i+1) ans=n×i=1k(Xi+1)
为什么是 X i + 1 X_i+1 Xi+1 呢,举个例子,如果 n n n 只有 3 个质因子,那么 a n s ans ans n × ( 1 + X 1 + X 2 + X 3 + X 1 X 2 + X 1 X 3 + X 2 X 3 + X 1 X 2 X 3 ) n\times (1+X1+X2+X3+X1X2+X1X3+X2X3+X1X2X3) n×(1+X1+X2+X3+X1X2+X1X3+X2X3+X1X2X3),将其化简为多项式乘法就是 n × ( X 1 + 1 ) ( X 2 + 1 ) ( X 3 + 1 ) n\times(X1+1)(X2+1)(X3+1) n×(X1+1)(X2+1)(X3+1)。当然可以类比到质因数个数更多的情况。

如果你能看完这篇文章,我将发自内心地佩服你。

AC代码

#include <iostream>
#include <cstdio>

using namespace std;

typedef long long ll;

ll n;

ll solve ()
{
    ll ans = n;
    for (ll 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;
    }
    return ans;
}

int main()
{
    cin >> n;
    cout << solve();
    return 0;
}

参考资料与后记

粉兔题解:https://www.cnblogs.com/PinkRabbit/p/8278728.html
本文可以视作粉兔题解的注释版(%%%%PinkRabbit),因为笔者在学习数论时其实是较为吃力的,所以我就写下了这篇注释性质的题解供自己以后复习使用也给大家参考。由于本文写作于深夜,如果本文中出现您不理解的地方或者是笔误的地方请联系笔者修改!

<think>好的,用户需要查找关于P5785 [SDOI2012] 任务安排的题解或解析。首先,我应该回忆一下这个题目的内容。这是一道动态规划优化的问题,可能涉及斜率优化或者凸包优化。题目大致是将任务分批处理,每批任务有一个启动时间,需要最小化总成本。 接下来,我需要检查用户提供的引用内容是否有相关的信息。引用[1]提到了并查集和另一道题目,可能与本题无关。引用[2]提到了一种O(n√n)的算法,但不确定是否适用于此任务安排问题。引用[3]是关于kubeneter的,明显不相关。因此,用户提供的引用可能没有直接帮助,需要依靠其他知识。 题目模型通常是这样的:有n个任务,每个任务需要时间t_i和费用c_i。将任务分成若干批,每批开始前有S的启动时间。每批的总费用是该批完成时刻乘以该批任务的总费用系数。目标是最小化总费用。 动态规划的状态设计一般是dp[i]表示处理前i个任务的最小费用。转移方程需要考虑分批次,可能会是dp[i] = min(dp[j] + (sumT[i] + S * k) * (sumC[i] - sumC[j])),其中k是分了几批,但这里可能需要更巧妙的处理,比如将启动时间的影响提前计算,以避免依赖批次数量。这种方法叫做“费用提前计算”。 时间复杂度方面,如果直接使用O(n^2)的DP,对于n较大的情况会超时,所以需要优化。常见的优化方法包括斜率优化,将转移方程转换为线性形式,维护一个凸包,利用单调队列来快速找到最优转移点。 具体步骤可能是这样的: 1. 预处理前缀和数组sumT和sumC。 2. 设计状态转移方程,考虑费用提前计算,例如将后续的启动时间S的影响提前加到当前批次。 3. 将转移方程转换为斜率优化的形式,比如形如dp[j] = (dp[i] - ...) 的表达式,找到斜率相关的项。 4. 维护一个单调队列,保持候选转移点的斜率单调,从而每次可以在O(1)时间内找到最优的j。 需要注意的点包括斜率计算时的除法可能导致的精度问题,可以用交叉相乘来比较斜率的大小。此外,sumT可能不是单调的,这时候可能需要维护凸包的下凸壳,或者使用其他方法如CDQ分治,但题目中可能由于数据范围允许而采用单调队列优化。 可能还需要考虑sumT的前缀和是否会有负数,这会影响时间复杂度的分析。如果sumT是单调递增的,那么可以使用单调队列,否则可能需要更复杂的结构。 最后,根据这些分析,给出具体的状态转移方程和优化步骤,以及可能的代码结构。同时,提醒用户注意数据范围和可能的实现细节,比如使用long long类型避免溢出。</think>### P5785 [SDOI2012] 任务安排 题解 #### 题目分析 题目要求将$n$个任务分批处理,每批任务启动需要时间$S$,每批的总费用为该批完成时刻乘以其费用系数之和。目标是设计动态规划算法并用斜率优化实现$O(n)$时间复杂度。 #### 关键思路 1. **费用提前计算**:将后续分组的启动时间影响提前计入当前状态,转移方程为: $$dp[i] = \min_{0 \leq j < i} \{ dp[j] + sumT[i] \cdot (sumC[i] - sumC[j]) + S \cdot (sumC[n] - sumC[j]) \}$$ 其中$sumT$和$sumC$为前缀和数组[^1]。 2. **斜率优化**:将方程改写为线性形式,设$j$为最优转移点,则有: $$dp[j] = (sumT[i] + S) \cdot sumC[j] + dp[i] - sumT[i] \cdot sumC[i] - S \cdot sumC[n]$$ 转化为$y = kx + b$形式后,维护单调队列中的凸包点,保证斜率$sumC[j]$单调递增[^2]。 #### 实现步骤 1. **预处理前缀和**: ```python sumT = [0] * (n+1) sumC = [0] * (n+1) for i in 1..n: sumT[i] = sumT[i-1] + t[i] sumC[i] = sumC[i-1] + c[i] ``` 2. **维护单调队列**: - 在队列中删除斜率小于当前斜率的点 - 取队首作为最优转移点 - 将新点加入队列,维护凸包形状 #### 参考代码片段 ```python q = deque() q.append(0) dp[0] = 0 for i in 1..n: # 删除队首非最优项 while len(q) >= 2 and slope(q[0], q[1]) <= sumT[i] + S: q.popleft() j = q[0] dp[i] = dp[j] + sumT[i]*(sumC[i]-sumC[j]) + S*(sumC[n]-sumC[j]) # 维护凸包 while len(q) >= 2 and slope(q[-2], q[-1]) >= slope(q[-1], i): q.pop() q.append(i) ``` #### 复杂度分析 通过斜率优化将时间复杂度从$O(n^2)$降为$O(n)$,适用于$n \leq 3 \times 10^5$的数据范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oier_Asad.Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值