[luogu P6042]「ACOI2020」学园祭 题解
按照套路,像这样一大堆 \(\sum\) 套在一起的极其吓唬人的式子,一般都有以下几种解决方式:
-
推式子,可能式子会变得比较简单可做,单纯的推式子题一般需要用一些比较高级的数学知识或者一些奇思妙想来解决。
-
考虑转化式子的含义,从而简化求解过程。
-
对于数据范围比较小的,可以考虑每增加一个的影响, \(O(1)\) 转移,递推求解。
-
……
这道题 \(n\) 的范围只有1e6,于是考虑可不可以递推求解。
推导
首先直接看原式肯定啥都看不出来,我们直接代入他所给的那个函数的定义,原式即可化为:
\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i}\sum\limits_{k=1}^{j}\, \gcd(\,(i-j)!\,, \, (j-k)!\,)
\]
注意:题目中的函数定义了 \(0!=1\)
发现第二个 \(\sum\) 的范围被第 \(i\) 限制,第三个 \(\sum\) 的范围又被 \(j\) 限制。
所以我们需要考虑 \(i\) 每变大 1 所带来的影响。
例如:
当 \(i=3\) 时,答案会在原有基础上增加以下内容:
原式 | 等价于 |
---|---|
\(\gcd (\,(3-1)!\, , \, (1-1)!\,)\) | \(\gcd (\, 2! \, , \, 0! \,)\) |
\(\gcd (\,(3-2)!\, , \, (2-1)!\,)\) | \(\gcd (\, 1! \, , \, 1! \,)\) |
\(\gcd (\,(3-2)!\, , \, (2-2)!\,)\) | \(\gcd (\, 1! \, , \, 0! \,)\) |
\(\gcd (\,(3-3)!\, , \, (3-1)!\,)\) | \(\gcd (\, 0! \, , \, 2! \,)\) |
\(\gcd (\,(3-3)!\, , \, (3-2)!\,)\) | \(\gcd (\, 0! \, , \, 1! \,)\) |
\(\gcd (\,(3-3)!\, , \, (3-3)!\,)\) | \(\gcd (\, 0! \, , \, 0! \,)\) |
再考虑当 \(i=4\) 时,答案会在原有基础上增加以下内容:
原式 | 等价于 |
---|---|
\({\color{Red}{ \gcd (\,(4-1)!\, , \, (1-1)!\,)}}\) | \({\color{Red}{ \gcd (\, 3! \, , \, 0! \,)}}\) |
\({\color{Red}{ \gcd (\,(4-2)!\, , \, (2-1)!\,)}}\) | \({\color{Red}{ \gcd (\, 2! \, , \, 1! \,)}}\) |
\(\gcd (\,(4-2)!\, , \, (2-2)!\,)\) | \(\gcd (\, 2! \, , \, 0! \,)\) |
\({\color{Red}{ \gcd (\,(4-3)!\, , \, (3-1)!\,)}}\) | \({\color{Red}{ \gcd (\, 1! \, , \, 2! \,)}}\) |
\(\gcd (\,(4-3)!\, , \, (3-2)!\,)\) | \(\gcd (\, 1! \, , \, 1! \,)\) |
\(\gcd (\,(4-3)!\, , \, (3-3)!\,)\) | \(\gcd (\, 1! \, , \, 0! \,)\) |
\({\color{Red}{ \gcd (\,(4-4)!\, , \, (4-1)!\,)}}\) | \({\color{Red}{ \gcd (\, 0! \, , \, 3! \,)}}\) |
\(\gcd (\,(4-4)!\, , \, (4-2)!\,)\) | \(\gcd (\, 0! \, , \, 2! \,)\) |
\(\gcd (\,(4-4)!\, , \, (4-3)!\,)\) | \(\gcd (\, 0! \, , \, 1! \,)\) |
\(\gcd (\,(4-4)!\, , \, (4-4)!\,)\) | \(\gcd (\, 0! \, , \, 0! \,)\) |
发现 \(i=4\) 时答案的增加量是在 \(i=3\) 时的答案增加量的基础上增加了一部分(已标红)而来的。
我们把标红部分提取出来:
\(i=4: 0!, \, 1!, \, 1!, \, 0!\)
如果你继续往下写:
\(i=5: 0!, \, 1!, \, 2!, \, 1!, \, 0!\) |
---|
\(i=6: 0!, \, 1!, \, 2!, \, 2!, \, 1!, \,0!\) |
\(i=7: 0!, \, 1!, \, 2!, \,3!, \, 2!, \, 1!, \,0!\) |
\(i=8: 0!, \, 1!, \, 2!, \,3!, \, 3!, \, 2!, \, 1!, \,0!\) |
\(i=9: 0!, \, 1!, \, 2!, \,3!, \,4!, \, 3!, \, 2!, \, 1!, \,0!\) |
....... |
你会发现,每一次答案增加量的增加量的增加量为
\[{\color{Blue}{ i \; mod \; 2 == 1 \; ? \; (\left \lfloor \frac{i-1}{2} \right \rfloor)! \; : \; (\left \lfloor \frac{i-2}{2} \right \rfloor)!}}
\]
然后我们就可以愉快地递推啦。
这题在数学题里算是挺好做的了。
定义 \(f[i]\) 为 \(i\) 的答案,\(g[i]\) 为 \(i\) 的答案的增加量,\(res\) 为答案增加量的增加量。(其实 \(g[i]\) 也没必要开个数组,直接开个 \(res\) 一样的中间变量记一下就行)。
具体转移请参见代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10086001;
ll f[maxn];
ll g[maxn];
ll jc[maxn];
ll res;
int main() {
jc[0] = 1;
for (int i = 1; i <= 1000000; ++i) jc[i] = jc[i - 1] * i % mod;
f[1] = 1;
g[1] = 1;
res = 1;
for (int i = 2; i <= 1000000; ++i) {
res = res + (i & 1 ? jc[(i - 1) / 2] : jc[(i - 2) / 2]);
if (res >= mod) res %= mod;
g[i] = g[i - 1] + res;
if (g[i] >= mod) g[i] %= mod;
f[i] = f[i - 1] + g[i];
if (f[i] >= mod) f[i] %= mod;
}
int T;
cin >> T;
while (T--) {
ll n;
scanf("%lld", &n);
cout << f[n] << '\n';
}
return 0;
}
$$We're \; here \; to \; put \; a \; dent \; in \; the \; universe.$$