题意
有一个排列p,需要先将其在中间分割开,分割成非空的两个部分,然后每个p[i]有一个权值a[i],意思是把p[i]从一个集合调整到另一个集合的代价是a[i],要求最后第一个集合里的所有数都比第二个集合里的数小。球最小花费
数据范围
解法
考虑枚举最终第一个集合的大小,从0到n,首先在大小为0时,答案就是a[1](大小为n时答案就是a[n])。
现在需要着重处理的就是大小1~(n-1)的答案:
我们用线段树维护在每个位置断开的答案,枚举现在集合的大小,那么小于等于集合大小的数如果在当前位置的右边就会产生贡献,大于集合大小的数在左边会产生贡献。然后统计一下就好了
(程序copy了wustdio大佬的)
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define lson o<<1
#define rson o<<1|1
#define mid (l+r)/2
using namespace std;
struct Tree
{
ll s0,s1;
ll ans;
}tree[800005];
int n,p[200005],pos[200005];
ll a[200005];
void pushup(int o)
{
tree[o].s0=tree[lson].s0+tree[rson].s0;
tree[o].s1=tree[lson].s1+tree[rson].s1;
tree[o].ans=min(tree[lson].ans+tree[rson].s0,tree[rson].ans+tree[lson].s1);
tree[o].ans=min(tree[o].ans,tree[lson].s1+tree[rson].s0);
}
void build(int o,int l,int r)
{
if(l==r)
{
tree[o].s1=a[l];
tree[o].ans=1e18;
return;
}
build(lson,l,mid);
build(rson,mid+1,r);
pushup(o);
}
void update(int o,int l,int r,int p)
{
if(l==r)
{
tree[o].s0=tree[o].s1;
tree[o].s1=0;
return;
}
if(p<=mid)update(lson,l,mid,p);
else update(rson,mid+1,r,p);
pushup(o);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
pos[p[i]]=i;
}
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
build(1,1,n);
ll ans=tree[1].ans;//ans=a[1]
for(int i=1;i<=n;i++)
{
update(1,1,n,pos[i]);
ans=min(ans,tree[1].ans);
}
printf("%lld\n",ans);
return 0;
}