题目链接:http://codeforces.com/contest/1295/problem/E
题意:给定一个排列,以及每个位置的价值a[]。现在把当前排列分成前后两部分,可以将左边集合的数扔到右边集合,但需要消费该数对应的代价;同理可以将右边集合的数扔到左边集合,但需要消费该数对应的代价。通过若干次移动操作,实现左边集合数恒小于右边集合数。求最小花费。
题解:最终实现左边集合数恒小于右边集合数,等价于左边数,右边数,对于当前选定的,我们判断每个划分位置,我们将的数放在左集合,将的数放在右集合。那么对于的数需要从右集合扔到左集合;对于的数需要从左集合扔到右集合。
用数组来表示对于当前,下标划定为需要的最小代价,从0到n枚举,每次增加1,重点考究每次增加1时的更新,可以发现,当增加为后,原本的位置都减少代价,的位置增加了代价。该过程可以用线段树实现。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn=200010;
ll mn[maxn<<2],lazy[maxn<<2];
int a[maxn],n;
ll b[maxn];
int pos[maxn];
void pushup(int rt){
mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);
}
void pushdown(int rt,int l,int r){
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
int mid=(l+r)>>1;
mn[rt<<1]+=lazy[rt];
mn[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
void build(int rt,int l,int r){
lazy[rt]=0;
if(l==r){
mn[rt]=b[l];return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int a,int b,int val){
if(l>=a&&r<=b){
mn[rt]+=val;
lazy[rt]+=val;
return;
}
if(lazy[rt]) pushdown(rt,l,r);
int mid=(l+r)>>1;
if(a<=mid) update(rt<<1,l,mid,a,b,val);
if(mid<b) update(rt<<1|1,mid+1,r,a,b,val);
pushup(rt);
}
ll query(int rt,int l,int r,int a,int b){
if(l>=a&&r<=b){
return mn[rt];
}
if(lazy[rt]) pushdown(rt,l,r);
int mid=(l+r)>>1;
ll ans=inf;
if(a<=mid) ans=min(ans,query(rt<<1,l,mid,a,b));
if(mid<b) ans=min(ans,query(rt<<1|1,mid+1,r,a,b));
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1,p;i<=n;i++){
scanf("%d",&p);
pos[p]=i;
}
b[0]=a[0]=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=b[i-1]+a[i];
}
build(1,1,n-1);
ll ans=query(1,1,n-1,1,n-1);
for(int i=1;i<=n;i++){
int p=pos[i];
if(p<n)
update(1,1,n-1,p,n-1,-a[p]);
if(p>1)
update(1,1,n-1,1,p-1,a[p]);
ans=min(ans,query(1,1,n-1,1,n-1));
}
printf("%I64d\n",ans);
}
来源:CSDN
作者:罗gkv
链接:https://blog.csdn.net/weixin_43918473/article/details/104285164