题解:AT_abc467_e [ABC467E] Adjacent Sums (hard)

题目链接:洛谷 AtCoder


分享一种神秘且麻烦的做法。

首先随意地构造一组 \(c_1,c_2,\dots ,c_n\),使得 \(c_i+c_{i+1}=b_i\)。一种简单的构造方法是令 \(c_1=0\),后面的多退少补。

然后因为 \(a_i+a_{i+1} \equiv b_i \Leftrightarrow a_i+a_{i+1}-b_i \equiv 0 \Leftrightarrow (a_i-c_i)+(a_{i+1}-c_{i+1}) \equiv 0\),所以题目条件就可以看成给 \(a_i+c_i\) 加上总和最小数,使得相邻两项的和能被 \(M\) 整除。下文所述的 \(a_i\) 即为 \(a_i+c_i\)

发现最终的序列取模后一定是形如 \(x,m-x,x,m-x,x,\dots\) 的,所以如果钦定 \(a_1\) 最后的值(设其为 \(v\)),就能把整个序列还原。

现在考虑如何快速对于一个 \(v\) 算出答案。先把奇数项和偶数项分开,则奇数项都要变成 \(v\),偶数项都要变成 \(m-v\)。只保留奇数项,那么小于等于 \(v\)\(a_i\) 需要变成 \(v\),大于 \(v\)\(a_i\) 需要变成 \(m+v\)。将 \(a\) 排序后找到最后一个小于等于 \(v\) 的项 \(a_k\),再设奇数项共有 \(h\) 个,那么总代价就是 \(h\times v+(h-k)\times m-\sum a_i\)。二分找到 \(k\),然后直接计算即可。偶数项同理。

但是 \(v\) 的可能取值是 \(10^9\) 级别的,无法一个个枚举。通过上面的式子容易发现一个能取到最小值的 \(v\) 必然等于 \(0\),或某个 \(a_i\)(奇数项)或 \(m-a_i\)(偶数项),因为如果 \(v\) 在两个上述值之间,将其减一后含 \(m\) 的项不会变(\(k\) 不变),而含 \(v\) 的项不变(奇偶的 \(h\) 相等)或变小 (奇项的 \(h\) 多一)。

最终的做法就是拿 \(v\) 的每个可能取值试一试,求最小值。需要注意的是 \(v=m\) 的答案需要特判,因为此时 \(a_i=0\) 的项不用变化。然后要开 long long。

代码实现的时候把二分换成了双指针。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
#define int long long

int n,m,a[N],b[N*2],f[N*2];
vector<int> c;

void solve1()
{
    for(int i=1;i<=n;i+=2)
        c.push_back(a[i]);
    sort(c.begin(),c.end());
    auto it=c.begin();
    for(int i=1;i<=n;i++)
    {
        while(it!=c.end()&&*it<=b[i])
            it++;//双指针找到 k
        f[i]=1ll*(c.end()-it)*m+1ll*(n+1)/2*b[i];
    }
}

void solve2()
{
    c.clear();
    for(int i=2;i<=n;i+=2)
        c.push_back(a[i]);
    sort(c.begin(),c.end());
    auto it=c.begin();
    for(int i=n;i>=1;i--)
    {
        b[i]=m-b[i];//偶数项的 v 实际是 m-v
        while(it!=c.end()&&*it<=b[i])
            it++;
        f[i]+=1ll*(c.end()-it)*m+1ll*n/2*b[i];
    }
}

signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=2;i<=n;i++)
    {
        scanf("%lld",&b[i]);
        (b[i]-=b[i-1])%=m;
        (a[i]+=m-b[i])%=m;//将 a[i] 变成 a[i]-c[i]
    }
    for(int i=1;i<=n;i+=2)
        b[i]=a[i];
    for(int i=2;i<=n;i+=2)
        b[i]=m-a[i];
    sort(b+1,b+n+1);
    solve1();//计算奇数项
    solve2();//计算偶数项
    int sum=0,ans=0;
    for(int i=1;i<=n;i++)
    {
        sum+=a[i];
        if(a[i])
            ans+=m-a[i];
    }//特判 v==n
    for(int i=1;i<=n;i++)
        ans=min(ans,f[i]-sum);
    printf("%lld",ans);
    return 0;
}
posted @ 2026-08-24 19:03  ShanLing3  阅读(4)  评论(0)    收藏  举报
//雪花飘落效果