通过位移算子将差分与相邻求和转化为多项式乘法,利用 (1+x) 和 (1-x) 的组合找出规律,从而做到 $O(n)$ 的线性求解。
OJ: roj
题目 ID: 10017
难度:提高+/省选-
标签:组合数学数学
创建: 2026-10-10 07:11
更新: 2026-10-10 07:11
目录
形式化题目
给定一个长度为
- 如果当前数组
的长度 为偶数,则 ( )。 - 如果当前数组
的长度 为奇数,则 ( )。
经过
数据范围
- 对于 40% 的数据,满足
。 - 对于 100% 的数据,满足
, 。
其他约束
- 题面未给出具体的时空限制,常规可假设为 1000ms / 128MB(
config.json的"memory": 128)。 - 输入为单组数据。第一行为
,接下来一行为 个整数。
正解
思路
朴素的做法是直接模拟上述过程,因为每次操作长度减
本题可以通过多项式和位移算子
- 长度为偶数时的操作(
)相当于对原数组乘上多项式 。 - 长度为奇数时的操作(
)相当于对原数组乘上多项式 。
最后数组剩下的唯一一个元素,相当于把原数组
我们分析
-
如果
是奇数(设 ): 操作的多项式依次为 , , , … 并以 结尾。 总共有 步,恰好包含了 次 和 次 。 所以 。 将式子二项式展开,非零项只能是偶数次项 ,其系数为 。奇数次项系数则为 。 -
如果
是偶数(设 ): 操作的多项式依次为 , , … 并以 结尾。 总共有 步,包含了 次 和 次 。 所以 。 展开后可以看出, 和 拥有相同的系数,均为 。
利用上述公式,我们可以很轻松地通过组合数直接求出
代码
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-10 08:04
# update_at: 2026-10-10 08:42
import sys
MOD = 1_000_000_007
type FactList = list[int] # 组合数预处理数组,下标 i 存 i! 或其逆元
def build_combinations(n: int) -> tuple[FactList, FactList]:
"""预处理阶乘与阶乘逆元,用来 O(1) 求组合数。"""
fact = [1] * (n + 1)
inv_fact = [1] * (n + 1)
for i in range(1, n + 1):
fact[i] = fact[i - 1] * i % MOD
inv_fact[n] = pow(fact[n], MOD - 2, MOD)
for i in range(n - 1, -1, -1):
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD
return fact, inv_fact
def coefficient(n: int, pos: int, fact: FactList, inv_fact: FactList) -> int:
"""第 pos 个数(从 1 开始)在最终答案中的系数:C(⌈n/2⌉-1, ⌈pos/2⌉-1) 带 (−1)^(⌈pos/2⌉-1)。"""
if n % 2 == 1 and pos % 2 == 0:
return 0 # n 为奇数时 (1−x²)^k 只有偶次项,奇数下标全为 0
half_pos = (pos + 1) // 2
half_n = (n + 1) // 2
choose = fact[half_n - 1] * inv_fact[half_pos - 1] % MOD * inv_fact[half_n - half_pos] % MOD
return -choose if half_pos % 2 == 0 else choose
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
try:
n = next(data)
except StopIteration:
return
fact, inv_fact = build_combinations(n)
values = [next(data) for _ in range(n)]
answer = sum(value * coefficient(n, pos, fact, inv_fact)
for pos, value in enumerate(values, 1)) % MOD
print(answer)
if __name__ == "__main__":
solve()/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 08:04
* update_at: 2026-10-10 08:40
*/
#include <iostream>
using namespace std;
typedef long long ll;
const ll MOD = 1000000007LL;
const int MAXN = 200000 + 5;
ll fact[MAXN]; // fact[i] = i! mod MOD
ll inv_fact[MAXN]; // inv_fact[i] = (i!)^{-1} mod MOD
ll quick_power(ll base, ll exp) {
ll res = 1;
base %= MOD;
while (exp > 0) {
if (exp % 2 == 1) {
res = res * base % MOD;
}
base = base * base % MOD;
exp /= 2;
}
return res;
}
void init_comb(int n) {
fact[0] = 1;
for (int i = 1; i <= n; i++) {
fact[i] = fact[i - 1] * i % MOD;
}
inv_fact[n] = quick_power(fact[n], MOD - 2);
for (int i = n - 1; i >= 0; i--) {
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
}
}
ll comb(int n, int m) {
if (m < 0 || m > n) {
return 0;
}
return fact[n] * inv_fact[m] % MOD * inv_fact[n - m] % MOD;
}
// 第 pos 个数(从 1 开始)在最终答案中的系数。
ll coefficient(int n, int pos) {
if ((n % 2 == 1) && (pos % 2 == 0)) {
return 0;
}
int half_pos = (pos + 1) / 2;
int half_n = (n + 1) / 2;
ll c = comb(half_n - 1, half_pos - 1);
if (half_pos % 2 == 0) {
return -c;
}
return c;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) {
return 0;
}
init_comb(n);
ll ans = 0;
for (int i = 1; i <= n; i++) {
ll a = 0; // 预置 0:输入被截断时不读未初始化值
cin >> a;
ans = (ans + (a % MOD) * coefficient(n, i)) % MOD;
}
cout << (ans + MOD) % MOD << '\n';
return 0;
}复杂度
- 时间复杂度:
。预处理阶乘及其逆元需要 的时间(加上一次求逆元的 ),随后遍历计算每个元素的系数并累加同样耗时 ,总体时间复杂度 。 - 空间复杂度:
。需要大小为 的数组来存储原数据序列,以及预处理的阶乘与逆元。
总结
- 这是一道经典地利用位移算子和多项式,把多轮交替的差分与相邻求和问题转化为组合数求解的题目。
- 解题关键是敏锐地发现多项式相乘后引发的奇偶消项效应:
。 - 代码实现细节:由于计算过程中系数有可能为负数,在 C++ 计算中应当正确处理对取模的影响,常采用
(val % MOD + MOD) % MOD。
验证
验证记录(R2,与官方 std.cpp 三方对拍)
⚠ 本题无本地
data/(data.json的files=[])⇒check_new_analysis报 「无 data/(跳过)」= 空转(T1/T65)。证据来源是官方std.cpp(★★★★)。
结论
main.cpp ≡ std.cpp ≡ main.py,在全部六类证据上 0 分歧 ⇒ 与官方 std.cpp 三方一致
(★ 不能写成「验证通过」)。
★ T67 语义核对(第一优先级)—— 对专家描述的订正
结论:专家的「多项式乘法 (1-x)(1+x)=1-x²」属实,但表述不完整。
std.cpp(42 行)不是逐轮模拟,而是闭式组合公式:对第 i 项(1-based)返回
| 条件 | 结果 |
|---|---|
n 奇 且 i 偶 |
0((1-x²)^k 无奇次项) |
| 否则 | c = C(⌈n/2⌉−1, ⌈i/2⌉−1),符号 (−1)^(⌈i/2⌉−1) |
代码为 cal():if(n&1 && !(m&1)) return 0; → m=(m+1)/2; n=(n+1)/2;
now=comb(n-1,m-1); m%2==0 ? -now : now。
| 核对项 | 结果 |
|---|---|
| ① 循环结构 | 单组(cin>>n 一次);拼两组输入 ⇒ 只输出 1 行,main.cpp 同 |
| ② 输入变量是否都参与 | n、a[] 都参与;无 no-op 变量 |
| ③ 输出格式 | ans mod 1e9+7 一行,endl |
| ④ 边界 | n=1 ⇒ a1;n=2 ⇒ a1+a2;n=1..2000 系数向量与 main.cpp 逐项相同 |
| ⑤ UB | ★ 空输入时 std 打印 679502094(int n 未初始化)⇒ std 自身 UB;main.cpp 有 if(!(cin>>n)) return 0;,安全 |
六类证据(T14 分级)
| 手法 | 规模 | 结果 |
|---|---|---|
| 真进程三方对拍 | 380 组(n=1..20 ∪ {99,100,101,1000,1001} 定值 + 随机 n≤60,含全 1e9、大质数 999999937、1+999999937 混合) |
0 分歧 |
| 真进程三方(大规模) | n ∈ {49999,50000,50001,77777,77778,99998,99999,100000} |
0 分歧 |
| 同进程 harness:系数向量 | n=1..2000 全部 i(≈200 万项)std_cal ≡ user_coef |
0 不匹配 |
| ★ V6 异源 brute(逐轮模拟题面规则,非公式) | n≤25 小值 2000 组 + 大值(≤1e9) 2000 组 |
0 分歧 |
| ★ 穷举(非抽样) | n≤10、a_i ∈ {1,2,3} 全枚举 88,572 组 |
0 分歧 |
| 多行输入 / CRLF / 多余空白 | — | 三者输出均 1000000000 ✓ |
| 奇偶覆盖 | 奇 n、偶 n 均单独覆盖 |
✓ |
V1 反事实表(300 组真进程对比 std)
| 变体 | 失败数 | 判定 |
|---|---|---|
关掉「奇 n 偶位置 ⇒ 0」 |
146/300(首个 n=3:1000000006 vs 1000000000) |
承重 |
关掉负号(half_pos%2==0 → false) |
286/300(首个 n=3:9) |
承重 |
去掉 (ans+MOD)%MOD |
148/300(首个 n=3:−7) |
承重 |
(pos+1)/2 改成 pos/2 |
300/300(首个 n=1:0 vs 5) |
承重 |
去掉 (a % MOD) |
0/300 | ★ 不承重(a ≤ 1e9 < MOD,恒等 no-op) |
关掉 comb 越界检查 |
0/300 | ★ 不承重(公式内 m ≤ n 恒成立) |
★ 探针有效性:4 个变体各失败 146–300 次 ⇒ 「0 失败」的 V1d/V1e 是真 no-op,
不是探针失效(T35 判据鉴别力)。
V9 UBSan + 对照组 · T38
- ★ 对照组(强制
INT_MIN取反)确实报错negation of -2147483648 cannot be represented…⇒ 探针有效;main.cppUBSan 跑多组(含n=99999全1e9、n=1e5随机)stderr 全空,-ftrapvrc=0 config.json:time=1000ms/memory=128MB。实测main0.01s / 8.4MB、std0.08s / 8.4MB、main.py0.05s / 34MB ⇒ 全部通过,内存 ≥3.7× 余量
与 std.cpp 的逐行差异点(7 条,均不影响语义)
std.cpp |
main.cpp |
影响 |
|---|---|---|
inv[] 线性递推求逆元 |
快速幂求 inv_fact[n] + 后缀递推 |
否(系数逐项相同) |
maxn=2e5+5,init() 全表预处理 |
init_comb(n) 只算到 n |
否(更快:0.01s vs 0.08s) |
cin>>n 无检查 |
if(!(cin>>n)) return 0; |
否(空输入安全性提升) |
ll a; 未初始化 |
ll a = 0;(本次修改) |
否(截断输入不读未初始化值) |
a * cal(...) |
(a % MOD) * coefficient(...) |
否(a<MOD,V1d 实测不承重) |
(ans+mod)%mod |
(ans+MOD)%MOD |
一致 |
| 单组输入 | 单组输入 | 一致 |
本次对 index.md 的两处修正(worker 提出,父会话机械核实后执行)
- ★ 「其他约束」原写
1000ms / **256MB**,而config.json是"memory": **128**⇒ 已改为 128MB(机械核实:grep '"memory"' config.json→128) - ★ 原「验证」节称「数据文件与官方 std 的推断结论完全一致」
—— 本题无本地
data/,该句无证据支撑(T65)⇒ 已删除该表述, 改为「与官方std.cpp在 388 组合成数据上三方一致(无本地 data/)」