Algorithm Notes

LJXin

记录算法刷题、竞赛思路与编程成长,希望每一篇都讲清楚一点、写扎实一点。

P10429 [蓝桥杯 2024 省 B] 拔河

P10429 [蓝桥杯 2024 省 B] 拔河

🔗 原题链接


📌 问题描述

题目

给定序列 a,选择两个不重叠的连续子段 [l1,r1] 和 [l2,r2](满足 r1 < l2),
使它们的区间和之差最小。

约束条件

\(1 \leq n \leq 10^3\),\(1 \leq a_i \leq 10^9\)

💡 核心思路

设两个子段分别为 [l1,r1] 和 [l2,r2],并满足 r1 < l2。

对于区间 [l,r] 的元素和,可以用前缀和快速计算:

sum(l,r) = pre[r] - pre[l-1]

暴力枚举两个区间的复杂度为 O(n^4),无法通过。

因此考虑枚举右区间的左端点 l2。

固定 l2 后,所有合法的左区间都必须满足 r1 < l2,
而右区间则形如 [l2,r2]。

于是问题转化为:
对于每个右区间和 x = sum(l2,r2),
在所有满足 r1 < l2 的左区间和中,找到一个最接近 x 的值。

为此可以维护一个有序集合 st,
其中存储当前所有满足 r1 < l2 的左区间和。

当 l2 从小到大递增时,之前右端点更小的左区间已经加入过 st,
因此新增加的合法左区间恰好是所有右端点为 l2-1 的区间,
将它们的区间和加入 st 即可。

然后枚举右端点 r2,计算右区间 [l2,r2] 的区间和 x,
再用 lower_bound 在 st 中找到第一个大于等于 x 的值 it。
由于集合有序,与 x 最接近的值只可能是 it 或 it 的前一个元素,
比较这两者即可更新答案。


📋 模板步骤

预处理前缀和

    vector<LL>a(n+1);
    vector<LL>pre(n+1,0);
    rep(i,1,n+1) cin>>a[i];
    rep(i,1,n+1) pre[i]=pre[i-1]+a[i]; //pre[1]=pre[0]+a[1] pre[n]=pre[n-1]+a[n]

    有序的st
        set<LL>st;
    枚举l2
    rep(l2,2,n+1){
        将新增的以l2-1为右端的字段和插入st
        rep(j,1,l2){// [j,l2-1] [0,0] [l2-1,l2-1]
            st.insert(pre[l2-1]-pre[j-1]);
        }
        枚举r2
        rep(r2,l2,n+1){ //[l2,r2] [l2,l2] [n,n]
            x=pre[r2]-pre[l2-1];
            寻找st中最接近的子段和
            auto it=st.lower_bound(x);
            如果it之前有数,则考虑在内
            if(it!=st.begin()) ans=min(llabs(*prev(it)-x),ans);
            把it考虑在内
            if(it!=st.end()) ans=min(llabs(*it-x),ans);
        }
    }

代码实现

完整代码

    #include <bits/stdc++.h>
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()
#define LL long long
#define MAX  INT_MAX
#define LMAX LLONG_MAX
#define rep(i,a,b) for(int i=(int)a;i<int(b);++i)
using namespace std;

void solve(){
    int n;
    cin>>n;
    vector<LL>a(n+1);
    vector<LL>pre(n+1,0);
    rep(i,1,n+1) cin>>a[i];
    rep(i,1,n+1) pre[i]=pre[i-1]+a[i]; //pre[1]=pre[0]+a[1] pre[n]=pre[n-1]+a[n]
    LL ans=LMAX,x=0;
    set<LL>st;
    rep(l2,2,n+1){
        rep(j,1,l2){// [j,l2-1] [0,0] [l2-1,l2-1]
            st.insert(pre[l2-1]-pre[j-1]);
        }
        rep(r2,l2,n+1){ //[l2,r2] [l2,l2] [n,n]
            x=pre[r2]-pre[l2-1];
            auto it=st.lower_bound(x);
            if(it!=st.begin()) ans=min(llabs(*prev(it)-x),ans);
            if(it!=st.end()) ans=min(llabs(*it-x),ans);
        }
    }
    cout<<ans<<'\n';
}   
int main(){

    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);

    int tc=1;
    //cin>>tc;
    while(tc--){
        solve();
    }
    return 0;
}

📊 复杂度分析

时间复杂度:\(O(n^2 \log n)\)

  • 外层枚举 l2:\(O(n)\)
  • 中层枚举 j:\(O(n)\),插入 set 为 \(O(\log n)\)
  • 内层枚举 r2:\(O(n)\),查询 set 为 \(O(\log n)\)

空间复杂度:\(O(n)\)

  • 数组存储及 set 中最多 \(O(n^2)\) 个元素

重点

st 中始终维护所有满足 r1 < l2 的左区间和;对当前右区间和 x,只需在 st 中找最接近 x 的值。

posted @ 2026-03-31 12:33  kliu_365  阅读(56)  评论(0)    收藏  举报
TOP