Adjacent Sums (hard)

Adjacent Sums (hard)

1. 问题重述

给定长度为 \(N\) 的序列 \(A\) 和长度为 \(N-1\) 的序列 \(B\),所有元素均在 \([0, M-1]\) 范围内。

每次操作可将任意 \(A_i\) 加 1。
目标:使所有相邻元素之和模 \(M\) 等于给定的 \(B_i\),即:

\[(A_i + A_{i+1}) \bmod M = B_i \]

求最小操作次数。


2. 最终序列的数学表示

设最终得到的序列为 \(A'\),并定义:

\[s = A'_1 \bmod M \]

由于相邻约束:

\[A'_i + A'_{i+1} \equiv B_i \pmod M \]

可以递推出,任意位置 \(i\) 的模值必须为:

\[A'_i \equiv C_i + (-1)^{i+1} \cdot s \pmod M \]

其中 \(C\) 数组定义为:

\[C_1 = 0,\quad C_{i+1} = B_i - C_i \]

注意:\(C_i\) 是整数,可以是负数或大于 \(M\),不一定在 \([0, M-1]\) 范围内。


3. \(C\) 数组与 \(D\) 数组的含义

\(C\) 数组:基准模板

  • \(C\) 代表当 \(s=0\)(即 \(A'_1 \equiv 0\))时,每个位置在整数域上必须达到的“理论值”。
  • 它完全由 \(B\) 序列决定,是求解的参考基准。

\(D\) 数组:当前值与基准的差值

\[D_i = C_i - A_i \]

  • \(D_i\) 表示当前位置的初始值 \(A_i\) 距离基准 \(C_i\) 的整数差距(可正可负)。
  • 当 \(s=0\) 时,\(A_i\) 需要增加的最小非负次数正好是:

\[D_i \bmod M \]


4. 代价函数 \(F(s)\)

对于任意选定的 \(s\)(\(0 \le s < M\)),位置 \(i\) 需要增加的操作次数为:

\[x_i(s) = \big(D_i + (-1)^{i+1} \cdot s\big) \bmod M \]

总操作次数:

\[F(s) = \sum_{i=1}^{N} x_i(s) \]

我们需要求:

\[\min_{0 \le s < M} F(s) \]


5. 核心优化:最优 \(s\) 只需在“零点”处枚举

为什么可以缩小范围?

  • 当 \(s\) 增加 1 时,每个 \(x_i(s)\) 要么增加 1(奇数位),要么减少 1(偶数位)。
  • 如果某个 \(s\) 使得所有 \(x_i(s)\) 都不为 0,那么向左或向右微调 \(s\)(即 \(s\pm1\)),要么会使总代价严格下降,要么会使总代价不变但最小项更接近 0。
  • 因此,全局最优解一定出现在某个位置的代价恰好为 0 的点上,即:

\[x_i(s) = 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)\)

先算出所有位置的初始代价和:

\[F(0) = \sum_{i=1}^{N} (D_i \bmod M) \]

6.2 普通变化量(\(base\))

当 \(s\) 增加 1 时:

  • 奇数位(\(i\) 为奇):\(x_i\) 通常 +1
  • 偶数位(\(i\) 为偶):\(x_i\) 通常 -1

所以基础净变化量为:

\[\text{base} = \#\{\text{奇数位置}\} - \#\{\text{偶数位置}\} = \begin{cases} 1 & (N \text{ 为奇数}) \\ 0 & (N \text{ 为偶数}) \end{cases} \]

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;
}
posted @ 2026-10-26 14:20  thenrify  阅读(7)  评论(0)    收藏  举报