区间 ±1 对差分数组只是单点 ±1,最少操作数为正槽位和与负槽位绝对值和的较大者,方案数为剩余缺口 +1。
OJ: roj
题目 ID: 3011
难度:提高
标签:差分贪心python
创建: 2026-10-01 09:56
更新: 2026-10-07 13:50
形式化题目
给定长度为
求:
- 使数列所有数变成同一个值所需的最少操作次数;
- 在操作次数最少的前提下,最终数列(即那个公共值)有多少种可能。
样例:
正解
思路
朴素想法:每次操作把一个区间整体 ±1,最小操作次数似乎要靠搜索或模拟贪心。困难来自一次区间操作同时改动
关键转化:看差分。令
| 区间操作 | 差分数组上的效果 |
|---|---|
| 只有 |
|
| 只有 |
也就是说,一次区间操作 = 在差分数组上选两个槽位,一个
设约束槽位中正数之和为
- 下界
:一次操作无论怎么选,正总量 至多减少 ,负总量 也至多减少 ,所以清零至少需要 步。 步一定够:前 步每次挑一正一负配对,各消 ;剩下 的单边缺口,每步用后缀/前缀区间操作消掉,配对端甩给自由槽。自由槽不参与"全部相等"的约束。
所以最少操作次数为
第二问:方案数。在
样例中差分为
代码
Python 版:
#!/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-01 09:56
# update_at: 2026-10-04 10:36
import sys
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
a = [next(data) for _ in range(n)]
# 差分槽位 b[i] = a[i] - a[i-1](a[0] = 0),下标 2..n 是必须清零的约束槽位;
# b[1] 与虚拟槽 b[n+1] 自由:前者唯一决定最终的公共值,后者吸收对后缀的操作
diff = [a[i] - a[i - 1] for i in range(1, n)] # 依次是 b[2..n]
pos = sum(x for x in diff if x > 0) # 正槽位之和:需要被 -1 操作消掉的量
neg = sum(-x for x in diff if x < 0) # 负槽位绝对值之和:需要被 +1 操作消掉的量
# 一对正负槽可被同一次区间操作各消 1,剩余的单边缺口靠自由槽吸收 → max(pos, neg)
print(max(pos, neg))
# 单边缺口的每次操作可任意分配给 b[1](改公共值)或 b[n+1](不改)→ 缺口 k 对应 k+1 种结果
print(abs(pos - neg) + 1)
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* 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-06 13:42
* update_at: 2026-10-06 13:42
*/
#include <cstdio>
#include <algorithm>
typedef long long ll;
const int MAXN = 1e5 + 5;
int n;
ll a[MAXN]; // 原数列,下标 1..n
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
// 差分 b[i] = a[i] - a[i-1](约定 a[0] = 0)。
// “所有数相等” <=> b[2..n] 全为 0;b[1] 只决定公共值,是自由槽;
// 虚拟槽 b[n+1] 也是自由槽,用来吸收后缀区间的另一端。
// 一次区间 [l, r] ±1 在差分数组上就是 b[l] 与 b[r+1] 一正一反的单点变化。
ll pos = 0; // b[2..n] 中正数之和:需要被减掉的总缺口
ll neg = 0; // b[2..n] 中负数绝对值之和:需要被加回来的总缺口
for (int i = 2; i <= n; ++i) {
ll d = a[i] - a[i - 1];
if (d > 0) pos += d;
else neg -= d;
}
// 一正一负槽可在同一次操作中配对各消 1,
// 剩下的单边缺口 |pos - neg| 用前缀/后缀区间操作甩给自由槽,
// 所以最少操作次数是 max(pos, neg)。
printf("%lld\n", std::max(pos, neg));
// 单边缺口的每一步既可落在 b[1](公共值 +1)也可落在 b[n+1](不变),
// k = |pos - neg| 步里自由分配,公共值可偏移 0..k,共 k+1 种结果。
printf("%lld\n", (pos > neg ? pos - neg : neg - pos) + 1);
return 0;
}复杂度
- 时间:读入与一次差分扫描,
; 轻松通过(本机全部测例约 s)。 - 空间:
存放数列与差分; ,Python 整数无溢出问题。
总结
- 区间操作题先看差分:区间 ±1 在差分数组上只是"两个端点单点反号变化",把对区间的贪心/搜索压缩成对槽位的计数。
- 全部相等
全零; (决定公共值)与 (虚拟槽)是两个可自由吸收变化的槽。 - 最少操作次数
:配对消减贴合下界,单边缺口甩给自由槽。 - 方案数
: 步单边操作每步可分配给"改公共值"的前缀区间,公共值可偏移 ,共 种。
图示解析
以样例
graph TD
A["a = [1, 1, 2, 2]<br/>b1=1 · b2=0 · b3=1 · b4=0"] --> B["路线一:后缀区间 [3,4] 减一<br/>b3 -= 1,配对端落虚拟槽 b5"]
A --> C["路线二:前缀区间 [1,2] 加一<br/>b3 -= 1,配对端落自由槽 b1 += 1"]
B --> D["b = [1, 0, 0, 0]<br/>a = [1,1,1,1],公共值 = 1"]
C --> E["b = [2, 0, 0, 0]<br/>a = [2,2,2,2],公共值 = 2"]
两条路线都只用了