1. 程式人生 > 實用技巧 >「ACOI2020」學園祭 題解

「ACOI2020」學園祭 題解

按照套路,像這樣一大堆 \(\sum\) 套在一起的極其嚇唬人的式子,一般都有以下幾種解決方式:

  1. 推式子,可能式子會變得比較簡單可做,單純的推式子題一般需要用一些比較高階的數學知識或者一些奇思妙想來解決。

  2. 考慮轉化式子的含義,從而簡化求解過程。

  3. 對於資料範圍比較小的,可以考慮每增加一個的影響, \(O(1)\) 轉移,遞推求解。

  4. ……

這道題 \(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;
}