题意
给出一个排列 p1,p2,...pn .初始时你需要选择一个位置把排列分成左右两个。然后在两个序列间移动元素使得左边序列的所有元素都比右边的所有元素小。给出每个元素 pi 从一个序列移动到另一个序列的代价 ai。求总代价最小值。
分析
先看最后两个集合的性质,假设左边的集合最大值是 k,那么左边的集合为 [1,k],右边的是 [k+1,n]
于是有一个 O(n3) 的思路,就是枚举切割点 i (1≤i<n),然后枚举左边集合的最大值 k,将小于等于 k 的值放入左边,大于 k 的放在右边。
考虑优化一下。
设原数组为 re[i]
我们每次枚举切割点 i 只会影响到一个值对答案的贡献。什么意思捏?
每次移动分割点,相当于将右边集合的一个数移到左边集合。
re[i] 这个值本来在右边集合的第一个数,现在变成了左边集合的最后一个数。
①:对于所有左边集合最大值是 [0,re[i]−1] 的答案,都会加上 v[i],因为本来不需要把这个数移到右边集合的,现在要把这个数移到右边集合
②:对于所有左边集合最大值是 [re[i],n] 的答案,都会减去 v[i],因为本来需要把这个数移到左边集合的,现在不需要把这个数移出集合
所以我们可以用线段树维护这些答案,每个叶子节点 [i,i],表示的是左边集合最大值为 k 的答案。
用线段树实现区间加,区间最小值即可。
代码如下
#include <bits/stdc++.h>
#define N 400005
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define LL long long
#define inf 2147483647
using namespace std;
LL val[N * 4], tag[N * 4], s[N], ss[N], ans = inf, z = 1;
int v[N], re[N], p[N];
LL ori[N];
void pushup(int rt){
val[rt] = min(val[rt << 1], val[rt << 1 | 1]);
}
void pushdown(int rt){
if(tag[rt]){
tag[rt << 1] += tag[rt];
tag[rt << 1 | 1] += tag[rt];
val[rt << 1] += tag[rt];
val[rt << 1 | 1] += tag[rt];
tag[rt] = 0;
}
}
void update(int l, int r, int rt, int a, int b, LL c){
if(l >= a && r <= b){
tag[rt] += c;
val[rt] += c;
return;
}
pushdown(rt);
int m = l + r >> 1;
if(a <= m) update(lson, a, b, c);
if(b > m) update(rson, a, b, c);
pushup(rt);
}
int main(){
int i, j, n, m;
scanf("%d", &n);
for(i = 1; i <= n; i++) scanf("%d", &j), re[i] = j, p[j] = i;
for(i = 1; i <= n; i++) scanf("%d", &v[i]);
for(i = 1; i <= n; i++) ss[i] = ss[i - 1] + v[p[i]];
for(i = 0; i <= n; i++) update(0, n, 1, i, i, ss[i]);//分割点为 0 的答案是前缀和
for(i = 1; i < n; i++){
update(0, n, 1, 0, re[i] - 1, v[i]);
update(0, n, 1, re[i], n, -v[i]);
ans = min(ans, val[1]);
}
printf("%lld", ans);
return 0;
}