题意:
给定一个排列p,每个位置有一个花费a[i],先可以在任意位置把p切开成头尾两部分,非空,然后从两个部分中选几个数放到另一个部分,使得左边集合的每个数都小于右边集合的最小值(某个集合为空也符合要求)。 移动第i个数花费为a[i],求最小花费。
(1<=n<=2e5)
-
我们观察发现,最终两个集合的答案只能是这样
1 2...m ; m+1,m+2..n
-
于是,我们可以枚举分割点t(t在左边),再枚举左边集合的最大值m,
[1,t]
中找到大于m的元素给他们移到右边去,[t+1,n]
中找到小于等于m的元素移到左边去。
总共O(n³)计算答案。 -
考虑优化,我们发现,如果我们记f[i]为左边最大值为i时的答案,当我们枚举了分割点t求出f[]数组,我们是可以转移得到分割点为t+1时的f[]数组答案的:
-
对于
i>=p[t+1],f[i]=f[i]-a[t+1]
因为之前需要花费a[t+1]把p[t+1]移动到左边,而现在分割完后p[t+1]已经在左边了,所以不需要花费这个代价。
对于i<p[t+1],f[i]=f[i]+a[t+1]
对于这些状态,p[t+1]应该在右边集合才对,在t处分割后已经在右边了,而在t+1处分割后位于左边,所以需要多话费a[t+1]。
每枚举一个分割点,(从1到n-1),就对f[]数组求一遍最小值,再用最小值去更新答案,也是取min。
我们需要一个可以维护最小值,维护区间加法的数据结构,可以用线段树。
具体做法:
先暴力求出分割点为1的时候的答案(枚举左边最大值),用线段树维护f[i]。
然后枚举分割点,转移,每次都是区间更新。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 2e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct node
{
#define lc rt<<1
#define rc rt<<1|1
int l,r;
ll val;
}tree[maxn<<2];
ll tag[maxn<<2];
inline void pushup(int rt)
{
tree[rt].val=min(tree[lc].val,tree[rc].val);
}
inline void build(int rt,int l,int r)
{
tree[rt].l=l,tree[rt].r=r;
if(l==r) return ;
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
inline void change(int rt,ll v)
{
tag[rt]+=v;
tree[rt].val+=v;
}
inline void pushdown(int rt)
{
if(tag[rt])
{
change(lc,tag[rt]);
change(rc,tag[rt]);
tag[rt]=0;
}
}
inline void upd(int rt,int vl,int vr,ll v)
{
int l=tree[rt].l,r=tree[rt].r;
if(r<vl || l>vr) return ;
if(vl<=l && r<=vr)
{
change(rt,v);
return ;
}
int mid=l+r>>1;
pushdown(rt);
upd(lc,vl,vr,v);upd(rc,vl,vr,v);
pushup(rt);
}
int p[maxn];
ll a[maxn];
int n;
ll pre[maxn];//1 2 3..i这些数移动的代价之和 不是a的前缀和
int main()
{
scanf("%d",&n);
build(1,0,n);
for(int i=1;i<=n;i++)
{
scanf("%d",p+i);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",a+i);
pre[p[i]]=a[i];
}
for(int i=1;i<=n;i++) pre[i]+=pre[i-1];
//当前分割点是1 枚举左边最大值求出答案 nlogn
//假设分割点是p[1] 排列被分成了 p[1] / p[2]p[3]..p[n]
for(int i=0;i<=n;i++)//左边最大值为i
{
if(i<p[1]) //左边应该有1 2 ...i
upd(1,i,i,pre[i]+a[1]);
else upd(1,i,i,pre[i]-a[1]);
}
//分割点不能为0 所以此时不能去更新ans
ll ans=tree[1].val;
for(int t=2;t<n;t++)//枚举分割点
{
int d=p[t];
upd(1,0,d-1,a[t]);
upd(1,d,n,-a[t]);
ans=min(ans,tree[1].val);
}
//空集的情况 就是线段树中0 n的位置
printf("%lld\n",ans);
return 0;
}