CF1295E Permutation Separation

题意

给出一个排列 p1,p2,...pnp_1,p_2,...p_np1,p2,...pn .初始时你需要选择一个位置把排列分成左右两个。然后在两个序列间移动元素使得左边序列的所有元素都比右边的所有元素小。给出每个元素 pip_ipi 从一个序列移动到另一个序列的代价 aia_iai。求总代价最小值。

分析

先看最后两个集合的性质,假设左边的集合最大值是 kkk,那么左边的集合为[1,k][1,k][1,k],右边的是 [k+1,n][k+1,n][k+1,n]
于是有一个 O(n3)O(n^3)O(n3) 的思路,就是枚举切割点 i (1≤i<n)i~(1\leq i<n)i (1i<n),然后枚举左边集合的最大值 kkk,将小于等于 kkk 的值放入左边,大于 kkk 的放在右边。
考虑优化一下。
设原数组为 re[i]re[i]re[i]
我们每次枚举切割点 iii 只会影响到一个值对答案的贡献。什么意思捏?
每次移动分割点,相当于将右边集合的一个数移到左边集合。
re[i]re[i]re[i] 这个值本来在右边集合的第一个数,现在变成了左边集合的最后一个数。
①:对于所有左边集合最大值是 [0,re[i]−1][0,re[i]-1][0,re[i]1] 的答案,都会加上 v[i]v[i]v[i],因为本来不需要把这个数移到右边集合的,现在要把这个数移到右边集合
②:对于所有左边集合最大值是 [re[i],n][re[i],n][re[i],n] 的答案,都会减去 v[i]v[i]v[i],因为本来需要把这个数移到左边集合的,现在不需要把这个数移出集合
所以我们可以用线段树维护这些答案,每个叶子节点 [i,i][i,i][i,i],表示的是左边集合最大值为 kkk 的答案。
用线段树实现区间加,区间最小值即可。

代码如下

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值