1. 程式人生 > 實用技巧 >[CF1295E] Permutation Separation - 線段樹,差分

[CF1295E] Permutation Separation - 線段樹,差分

Description

給定一個長度為 \(n\) 的排列,你可以將它切成兩段 \(A,B\),分別作為兩個集合,對於第 \(i\) 個元素,可以花費 \(a[i]\) 的代價把它移動到對面的集合中。求至少花費多少的代價,才能使得一個集合中的任意元素比另一個集合中的任意元素小。

Solution

列舉初態分界點 \(pos\) 和分界值 \(val\),設此時的答案為 \(f(pos,lim)\),暴力計算的話複雜度為 \(O(n^2)\)

考慮到 \(f(pos,lim)-f(pos-1,lim)=\sum_{lim < p_{pos}} a_{pos} - \sum_{lim \ge p_{pos}} a_{pos}\)

,我們用線段樹來維護對於某個確定的 \(pos\) 的所有 \(f(pos,lim)\),每次修改 \(pos\) 時,只需要將 \(1 \le lim < p_{pos}\) 部分 \(+a_{pos}\),將 \(p_{pos} \le lim \le n\) 部分 \(-a_{pos}\)。區間修改,區間詢問最小值。

特別注意,lim 是可以 =0 的。

#include <bits/stdc++.h>
using namespace std;

#define int long long 
const int N = 1000005;

struct SegmentTree 
{
    int a[N],tag[N];
    void Pushup(int p)
    {
        a[p]=min(a[p*2],a[p*2+1]);
    }
    void Put(int p,int val)
    {
        a[p]+=val;
        tag[p]+=val;
    }
    void Pushdown(int p)
    {
        if(tag[p])
        {
            Put(p*2,tag[p]);
            Put(p*2+1,tag[p]);
            tag[p]=0;
        }
    }
    void Modify(int p,int l,int r,int ql,int qr,int val)
    {
        if(l>qr||r<ql) return;
        if(l>=ql&&r<=qr)
        {
            Put(p,val);
        }
        else 
        {
            Pushdown(p);
            Modify(p*2,l,(l+r)/2,ql,qr,val);
            Modify(p*2+1,(l+r)/2+1,r,ql,qr,val);
            Pushup(p);
        }
    }
    int Query()
    {
        return a[1];
    }
} seg;

int n,a[N],b[N],p[N],f[N];

signed main()
{
    ios::sync_with_stdio(false);

    cin>>n;
    for(int i=1;i<=n;i++) cin>>p[i];
    for(int i=1;i<=n;i++) cin>>a[i];

    for(int i=1;i<=n;i++) 
    {
        // cerr<<"Modify "<<p[i]<<" "<<n<<" "<<a[i]<<endl;
        seg.Modify(1,0,n,p[i],n,a[i]);
    }
    int ans=1e18;
    for(int i=1;i<n;i++)
    {
        seg.Modify(1,0,n,0,p[i]-1,a[i]);
        seg.Modify(1,0,n,p[i],n,-a[i]);
        ans=min(ans,seg.Query());
    }
    cout<<ans<<endl;
}