Adjacent Sums (hard)
Adjacent Sums (hard)
1. 问题重述
给定长度为 \(N\) 的序列 \(A\) 和长度为 \(N-1\) 的序列 \(B\),所有元素均在 \([0, M-1]\) 范围内。
每次操作可将任意 \(A_i\) 加 1。
目标:使所有相邻元素之和模 \(M\) 等于给定的 \(B_i\),即:
求最小操作次数。
2. 最终序列的数学表示
设最终得到的序列为 \(A'\),并定义:
由于相邻约束:
可以递推出,任意位置 \(i\) 的模值必须为:
其中 \(C\) 数组定义为:
注意:\(C_i\) 是整数,可以是负数或大于 \(M\),不一定在 \([0, M-1]\) 范围内。
3. \(C\) 数组与 \(D\) 数组的含义
\(C\) 数组:基准模板
- \(C\) 代表当 \(s=0\)(即 \(A'_1 \equiv 0\))时,每个位置在整数域上必须达到的“理论值”。
- 它完全由 \(B\) 序列决定,是求解的参考基准。
\(D\) 数组:当前值与基准的差值
- \(D_i\) 表示当前位置的初始值 \(A_i\) 距离基准 \(C_i\) 的整数差距(可正可负)。
- 当 \(s=0\) 时,\(A_i\) 需要增加的最小非负次数正好是:
4. 代价函数 \(F(s)\)
对于任意选定的 \(s\)(\(0 \le s < M\)),位置 \(i\) 需要增加的操作次数为:
总操作次数:
我们需要求:
5. 核心优化:最优 \(s\) 只需在“零点”处枚举
为什么可以缩小范围?
- 当 \(s\) 增加 1 时,每个 \(x_i(s)\) 要么增加 1(奇数位),要么减少 1(偶数位)。
- 如果某个 \(s\) 使得所有 \(x_i(s)\) 都不为 0,那么向左或向右微调 \(s\)(即 \(s\pm1\)),要么会使总代价严格下降,要么会使总代价不变但最小项更接近 0。
- 因此,全局最优解一定出现在某个位置的代价恰好为 0 的点上,即:
由此可得候选 \(s\) 集合(枚举 \(i\)):
- 若 \(i\) 为奇数:\(s \equiv -D_i \pmod M\)
- 若 \(i\) 为偶数:\(s \equiv D_i \pmod M\)
候选 \(s\) 的数量至多为 \(N\) 个。
6. 差分更新:快速计算所有候选点的 \(F(s)\)
我们不直接对每个候选点重新求和(否则是 \(O(N^2)\)),而是从 \(s=0\) 出发,用差分维护代价变化。
6.1 计算起点 \(F(0)\)
先算出所有位置的初始代价和:
6.2 普通变化量(\(base\))
当 \(s\) 增加 1 时:
- 奇数位(\(i\) 为奇):\(x_i\) 通常 +1
- 偶数位(\(i\) 为偶):\(x_i\) 通常 -1
所以基础净变化量为:
6.3 特殊情况:越界突变
在基础变化之外,当某个 \(x_i(s)\) 越过模 \(M\) 边界时,会产生额外修正:
-
奇数位越界:从 \(M-1\) 变成 \(0\),实际变化相比“+1”少了 \(M\),所以需要 \(-M\) 修正。
触发位置:\(s\) 使得 \(x_i(s) = M-1\),即 \(s \equiv -D_i - 1 \pmod M\)。 -
偶数位越界:从 \(0\) 变成 \(M-1\),实际变化相比“-1”多了 \(M\),所以需要 \(+M\) 修正。
触发位置:\(s\) 使得 \(x_i(s) = 0\),即 \(s \equiv D_i \pmod M\)。
我们将这些事件记录为 (触发时刻 s, 修正值)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128;
ll mod(ll x, ll m) {
return (x % m + m) % m;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n; ll m; cin >> n >> m;
vector<ll> a(n + 1), b(n);
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i < n; i ++) cin >> b[i];
//c表示当起始点s=1时,每个点应该是什么值,d表示每个点的初始变化量
//计算过程中可能ci是负的
vector<ll> c(n + 1), d(n + 1); c[1] = 0; d[1] = -a[1];
i128 f = mod(d[1], m);
for(int i = 2; i <= n; i ++) {
c[i] = b[i - 1] - c[i - 1];
d[i] = c[i] - a[i];
f += mod(d[i], m);
}
//步长,s增大1,基础变化量(奇+ 偶-),跳跃点:变化量=0的点,事件点:发生变化的点
//对于奇数,跳跃点之前,变化量达到m-1,再走一步,变化量为m,此时要-m;对于偶数,达到跳跃点,变化量恰好为0,下一步-1,要+m
//奇偶不同,事件和跳跃就发生不同步。注意,跳跃点为0的时候,奇数可以不用处理,而偶数需要,所以把奇数的事件点前移一位
ll base = (n & 1) ? 1 : 0;
vector<pair<ll, ll>> event;
vector<ll> query;
for(int i = 1; i <= n; i ++) {
if(i & 1) {
ll cand = mod(-d[i], m);
ll es = mod(-d[i]-1, m);
event.emplace_back(es, -m);
query.emplace_back(cand);
}
else {
ll es = mod(d[i], m);
event.emplace_back(es, m);
query.emplace_back(es);
}
}
//query.emplace_back(m - 1); event.emplace_back(m - 1, 0);
sort(query.begin(), query.end());
sort(event.begin(), event.end());
query.erase(unique(query.begin(), query.end()), query.end());
i128 ans = (i128)4e36;
int cur = 0;
i128 step = 0;
for(auto & k : query) {
while(cur < event.size() && event[cur].first < k) step += event[cur++].second;
//奇数前移,而偶数的事件晚发生,所以小于k的
i128 p = f + step + (ll)k * base;
ans = min(p, ans);
}
cout << (ll)ans << endl;
return 0;
}

浙公网安备 33010602011771号