题解 P6060 【[加油武汉]传染病研究】

sochiji

2020-02-10 14:52:02

Solution

### 蒟蒻来抛砖引玉写一发50分的题解。 ## 定义 函数 $D(x),x\in\mathbb{Z_+}$表示$x$的约数个数。 >例:$D(6)=4,\quad D(7)=2$ ## 单个函数值的一般计算方法 设$x=p_1^{\alpha _1}\cdot p_2^{\alpha _2}\cdot ... \cdot p_k^{\alpha _k}$,其中$p_i$为素数且互不相同,$\alpha _i$为正整数。 对于$x$的任意正因数$d=p_1^{\beta _1}\cdot p_2^{\beta _2}\cdot ... \cdot p_k^{\beta _k}$和任意$1\leq i\leq k$,有$0\leq\beta _i\leq\alpha_i$; 根据乘法原理,$\displaystyle D(x)=\prod^k_{i=1}(\alpha _i+1)$ ## 性质 1. 若$x_1,x_2$互素,有$D(x_1\cdot x_2)=D(x_1)\cdot D(x_2)$; >证明: >设$\displaystyle x_1=\prod^k_{i=1}p_i^{\alpha_i},\quad x_2=\prod^{k+l}_{i=k+1}p_i^{\alpha_i}$; >由于对于任意$1\leq i\leq k,\quad k+1\leq j\leq k+l$,有$p_i\neq p_j$, >所以$\displaystyle D(x_1\cdot x_2)=\prod^{k+l}_{i=1}(\alpha _i+1)=\prod^{k}_{i=1}(\alpha _i+1)\cdot \prod^{k+l}_{i=k+1}(\alpha _i+1)=D(x_1)\cdot D(x_2)$; >**性质1**表明$D(x)$是**积性函数**。 2. 若$x=p^\alpha$且$p$为素数,则: - $D(x)=\alpha+1$; - $D(x^n)=\alpha\cdot n+1=n\cdot D(x)-n+1$; >证明: >第一条由上可知显然。 >第二条:$D(x^n)=D(p^{n\alpha})=\alpha\cdot n+1$ >$D(x^n)=n(D(x)-1)+1=n\cdot D(x)-n+1$ 3. 若$x=p^\alpha \cdot c$,$p$为素数且$p$与$c$互素,则$D(x^n)=(n\alpha +1)\cdot D(c^n)$; >证明: >$D(x^n)=D(p^{n\alpha}\cdot c^n)$; >因为$p$与$c$互素,所以$p^{n\alpha }$与$c^n$互素,进而有$D(p^{n\alpha}\cdot c^n)=D(p^{n\alpha})\cdot D(c^n)=(n\alpha +1)\cdot D(c^n)$; --- ## 使用线性欧拉筛法生成$k=1$时的函数值表 >阅读之前请确保已经对[与素数及欧拉筛法相关的知识](https://zhuanlan.zhihu.com/p/74477008)有简单的了解; 欧拉筛法使得每个合数仅能通过“它的**最小真因子**乘上它的**最大真因子**”这种方法被构造出,而素数不会被筛掉。我们构造出一个新的合数$x$时,有下面几种情况: 1. $x$本身是素数: >这种情况下$D(x)=2$ 2. $x$的最大真因子$p_2$是素数,且与最小真因子$p_1$**不相等**: >这种情况可以表示为$x=p_1\cdot p_2$,那么$D(x)=D(p_1\cdot p_2)=D(p_1)\cdot D(p_2)$ 3. $x$的最大真因子$p$是素数,且与最小真因子**相等**: >这种情况可以表示为$x=p\cdot p$,那么$D(x)=D(p^2)=3$ 4. $x$的最大真因子$c$是合数,且与最小真因子$p$**互素**: >这种情况可以表示为$x=p\cdot c$,那么$D(x)=D(p\cdot c)=D(p)\cdot D(c)=2\cdot D(c)$ 5. $x$的最大真因子$m$是合数,且$m$是$x$的最小真因子$p$的整数次方的**倍数**,即$m=p^\alpha \cdot c$: >这种情况可以表示为$x=p\cdot m=p\cdot p^{\alpha}\cdot c=p^{\alpha +1} \cdot c$,其中$p$与$c$互素即$\alpha =\mathrm{ord}_{p}m$,那么$D(x)=D(p^{\alpha +1}\cdot c)=D(p^{\alpha +1})\cdot D(c)=(\alpha +2)\cdot D(c)$ 为了使欧拉筛法可以递推$D(x)$,我们不仅得记录每个数的$D(x)$,还有必要记录它的: 1. 最小素因子$p$,代码中表示为`minfac`; 2. 最大阶$\alpha=\mathrm{ord}_px$,代码中表示为`order`; 3. 与$p$互素的`c`; 4. 函数值$D(x)$,代码中表示为`d` ```cpp struct rec { int minfac; int order; int c; int d; } dlist[10000001]; ``` 在欧拉筛法的代码中加入计算`dlist[]`的语句即可生成$k=1$时的$D(x^k)$函数值表。 由于此题的测试点的数据较多,可以通过记录$k=1$时函数值表的前$n$项和$\displaystyle\sum_{i=1}^{n}D(i)$来优化。 当$x$是$p$的整数次幂时有$c=1$,因此要注意初值条件: ``` dlist[1].c = 1; dlist[1].d = 1; ``` 线性筛代码: ```cpp //primeMap[]是素性映射表,primeList[]是素数表,sumList[]是k==1时函数值前缀和表 int getPrimeList(int n, bool primeMap[], int primeList[], int sumList[]) { //初始化 int count = 0; for (int i = 2; i <= n; i++) primeMap[i] = true; primeMap[0] = false; primeMap[1] = false; dlist[1].c = 1; dlist[1].d = 1; int half = n / 2; for (int i = 2; i <= half; i++) if (primeMap[i]) { dlist[i] = {i, 1, 1, 2}; //第1种情况 primeList[count] = i; count++; for (int j = 0; j < count && i >= primeList[j] && i * primeList[j] <= n; j++) { primeMap[i * primeList[j]] = false; if (i == primeList[j]) { dlist[i * primeList[j]] = {primeList[j], 2, 1, 3}; //第3种情况 break; } else dlist[i * primeList[j]] = {primeList[j], 1, i, dlist[i].d * dlist[primeList[j]].d}; //第2种情况 } } else for (int j = 0; j < count && i * primeList[j] <= n; j++) { primeMap[i * primeList[j]] = false; if (dlist[i].minfac == primeList[j]) //由于存储了i的最小素因子 故写法与普通线性筛不同 { dlist[i * primeList[j]] = {primeList[j], dlist[i].order + 1, dlist[i].c, (dlist[i].order + 2) * dlist[dlist[i].c].d}; //第5种情况 break; } else dlist[i * primeList[j]] = {primeList[j], 1, i, 2 * dlist[i].d}; //第4种情况 } for (int i = half + 1; i <= n; i++) if (primeMap[i]) { dlist[i] = {i, 1, 1, 2}; //第1种情况 primeList[count] = i; count++; } for (int i = 2; i <= n; i++) sumList[i] = sumList[i - 1] + dlist[i].d; //生成前缀和 return count; } ``` >注意:在非初始化处对`struct`进行整体赋值的语法在`C++11`标准之前是非法的。 据题意,我们应当在`main()`中调用上面的函数,并给参数`n`赋值$10^7$。之后我们就能从`sumList[]`立即输出任何$k=1$时的$\displaystyle\sum_{i=1}^{n}D(i)$。 --- ## 根据$k=1$时的$D(x)$函数值表生成$k$取任意值的$D(x^k)$函数值表 根据**性质3**,我们可以在$O(n)$的时间内递推出$\displaystyle\sum_{i=1}^{n}D(i^k)$全部项并求和。代码如下: ```cpp const int MODNUM = 998244353; long long dpow[10000001];//存放n^k的函数值,以n为索引 int getSum(int n, int k) { long long sum = 0; sum = 1; for (int i = 2; i <= n; i++) { dpow[i] = (k * dlist[i].order + 1) * dpow[dlist[i].c; sum += dpow[i]; sum %= MODNUM; } return (int)sum; } ``` 在`main()`中如下调用上面的函数并注意初值条件: ```cpp dpow[1] = 1; sumlist[1] = 1; int t; scanf("%d", &t); for (int i = 1; i <= t; i++) { int n, k; scanf("%d%d", &n, &k); printf("%d\n", (k == 1) ? sumlist[n] : getSum(n, k)); } ``` 不限制$k=1$时,总的时间复杂度为$O(T\cdot n)$,上面的方法最多需要进行$10^{11}$次加法运算,会导致5个测试点超时。 要AC此题,应该得以$O(T)$的时间复杂度解决整个问题,但本蒟蒻实在找不出$\displaystyle\sum_{i=1}^{n}D(i)$与$\displaystyle\sum_{i=1}^{n}D(i^k)$的关系(应该是一个高次多项式函数),所以这篇题解只值50分。 ## 感谢阅读。