定義 f(n) = (∏_{i=1}^n (i+1)) % 1,000,000,007。多組查詢 n (≤ 10⁷),輸出 f(n)。內存限制較小,無法直接打表存 10⁷ 個答案。
寫出前幾項:
f(1) = 2 f(2) = 2 * 3 = 6 f(3) = 6 * 4 = 24 f(4) = 24 * 5 = 120 ... f(n) = f(n-1) * (n+1) % MOD
設 sum = n+1 (下一個乘數),則:
f[1] = 1; // 題目定義從 1 開始?實際樣例需核對
sum = 2;
for i = 2..N:
f[i] = f[i-1] * sum % MOD;
sum = sum * (i+1) % MOD;
因無法存完整表,但查詢可離線:
(n, id)n 升序排序cur_n、cur_f、cur_sum,從小到大推進計算n,記錄答案時間複雜度:O(max_n + Q log Q),空間:O(Q)。
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;
const int MAXQ = 100005;
struct Query {
int n, id;
bool operator<(const Query& other) const { return n < other.n; }
} queries[MAXQ];
int ans[MAXQ];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, cnt = 0;
while (cin >> n) {
queries[cnt++] = {n, cnt};
}
if (cnt == 0) return 0;
sort(queries, queries + cnt);
long long f = 1; // f(1) = 1? 依題目實際定義
long long sum = 1; // 累積乘數
int cur = 1; // 當前已計算到的 n
for (int i = 0; i < cnt; ++i) {
int target = queries[i].n;
while (cur <= target) {
f = f * sum % MOD;
sum = sum * (cur + 1) % MOD;
++cur;
}
ans[queries[i].id] = f;
}
for (int i = 1; i <= cnt; ++i) {
cout << ans[i] << '\n';
}
return 0;
}
| 技巧 | 作用 |
|---|---|
| 離線排序 | 將隨機查詢轉為有序流式計算,避免重複/回溯 |
| 線性推進 | 利用遞推關係單向計算,無需存儲中間結果 |
| 模運算優化 | 每步取模防止溢出,用 64 位中間變量 |
| 內存優化 | 只存查詢和答案 O(Q),不存 10⁷ 大表 |
題目給 f(n) = (∏_{i=1}^n (i+1)) % MOD = (n+1)! / 1! % MOD = (n+1)! % MOD。
代碼中 f = f * sum,sum = ∏_{j=1}^{cur} (j+1)?需仔細核對初始值。
實際樣例若無,建議按 f(1)=2, f(2)=6 調試初始 f=1, sum=2, cur=1:
cur=1: f=1*2=2, sum=2*2=4 cur=2: f=2*4=8? 應為 6。說明遞推式需修正。
正確遞推:f(n) = f(n-1) * (n+1),f(1)=2。
即 sum 直接取 n+1,無需累積:
f = 2; // f(1)
for (int i = 2; i <= target; ++i) {
f = f * (i + 1) % MOD;
}
long long f = 1; // f(0) = 1 (空積)
int cur = 0;
for (int i = 0; i < cnt; ++i) {
int target = queries[i].n;
while (cur < target) {
++cur;
f = f * (cur + 1) % MOD; // 乘以 (cur+1)
}
ans[queries[i].id] = f;
}
本題核心是離線查詢 + 線性遞推,將 O(Q × max_n) 降為 O(max_n + Q log Q)。關鍵在於識別遞推關係、正確處理初始值、利用排序將隨機訪問轉為順序計算。此技巧廣泛適用於「大範圍、可遞推、查詢離線」的問題。