E. Permutation Separation
题目链接:https://codeforces.com/contest/1295/problem/E
题意:
给你一个长度为n的数组,其中 1 ~ n 每个数恰好在该数组中出现一次,并且每个数都有自己的价值 ai
你要在数组中任意位置切一刀使数组划分为左集合和右集合(刚切完左右集合不得为空)
切完后你可以将一个集合的任意一个元素 x 丢到另一个集合去,但是你需要支付 a[x]的代价
现要使操作结束后右集合的元素均大于左集合的元素(或其中任意一集合为空集),问你最小代价是多少
分析:
先模拟一遍
假设 n = 6,数组的元素分别为 3,6,5,4,1,2 ,那么我们可以切的位置分别有3,6、6,5、5,4、4,1、1,2之间
我们将每一个可以切的间隙从左往右进行编号,并定义为切点,如图所示:
其中 1 左边的切点有①②③④,右边的切点有⑤
2 左边的切点有①②③④⑤,右边没有切点
3 左边没有切点,右边的切点有①②③④⑤
......
在开始之前,我们还要知道当我们操作完之后,左集合要么为空,要么剩余 m 个元素,而剩余的元素分别是1 ~ m
好了在了解完以上后,我们采用边枚举边更新的方法
首先,让我们用pos[i] 表示第 i 个元素的位置,其中pos[1] = 5,pos[2] = 6,pos[3] = 1...
用sum[i] 表示前 i 个位置的代价前缀和,其意义是将切点 i 左边的数全移至右边所付出的代价
初始我们先让第 i 个切点的代价为sum[i] , 即在切完之后将左边所有数都移至右集合。
我们用线段树维护每个切点的花费(第 i 个位置(区间)维护第 i 个切点),然后我们开始对每个元素进行枚举
one首先是元素 1 , pos[1] = 5 , 我们观察第四个元素右边的切点有⑤ , 左边的切点有①②③④
对于右边的切点⑤:它当前的花费为sum[5] ,包括了元素1的代价,但实际上在切完⑤之后元素1在左集合,且我们并不需要把元素1移至右边,所以我们把切点⑤的花费减去元素1的代价
对于左边的切点:在切完之后元素1是处于集合的右边,所以开始的时候sum[1],sum[2],sum[3],sum[4]并没有算上元素1右移的代价
而实际上元素1应该要移至左集合,所以我们把切点①~④的花费加上元素1的代价
two然后是元素2, pos[2] = 6 , 第六个元素右边没有切点,左边的切点有①②③④⑤
对于右边的切点:无。
对于左边的切点:切完之后元素2是处于集合的右边,所以开始的时候sum[1],sum[2],sum[3],sum[4],sum[5]并没算上元素2右移的代价
而实际上元素2应该要移至左集合,所以我们把切点①~⑤的花费加上元素2的代价
three紧接着是元素3,pos[3] = 1,第一个元素的左边没有切点,右边的切点有①②③④⑤
对于右边的切点:在切完之后元素3是处于集合的左边,且我们并不需要把元素3右移。
但在初始的时候我们统一把左集合的全都右移了,所以初始的时候切点①②③④⑤的花费都算上元素3的代价,所以我们要把他们的花费减去元素3的代价
对于左边的切点:无。
......
根据以上我们发现,对于第 i 次枚举(元素 i )编号为 1 ~ pos[i] - 1 的切点(元素 i 的右边)花费都要减去 i 的代价,而编号 pos[i] ~ n - 1的切点(元素i的左边)花费都要加上 i 的代价
这只要用线段树区间更新一波就可以把复杂度降至 logn,在加上枚举的时间消耗,总复杂度为 nlogn . 完全OK
ans 的值我们在每次枚举完更新
#include<bits/stdc++.h> using namespace std; #define ll long long const int N = 2e5 + 10; struct Segment_Tree{ ll l , r; ll minn , lazy; }tree[N << 2]; void build(ll l , ll r , ll rt , ll *aa) { tree[rt].l = l , tree[rt].r = r; if(l == r) { tree[rt].minn = aa[l]; return ; } ll mid = l + r >> 1; build(l , mid , rt << 1 , aa); build(mid + 1 , r , rt << 1 | 1 , aa); tree[rt].minn = min(tree[rt << 1].minn , tree[rt << 1 | 1].minn); } void push_down(ll rt) { if(tree[rt].lazy) { tree[rt].minn += tree[rt].lazy; tree[rt << 1].minn += tree[rt].lazy; tree[rt << 1 | 1].minn += tree[rt].lazy; tree[rt << 1].lazy += tree[rt].lazy; tree[rt << 1 | 1].lazy += tree[rt].lazy; tree[rt].lazy = 0; } } void update_range(ll L , ll R , ll val , ll rt) { if(tree[rt].r < L || tree[rt].l > R) return ; if(L <= tree[rt].l && R >= tree[rt].r) { tree[rt].lazy += val; tree[rt].minn += val; return ; } push_down(rt); ll mid = tree[rt].l + tree[rt].r >> 1; if(L <= mid) update_range(L , R , val , rt << 1); if(R > mid) update_range(L , R , val , rt << 1 | 1); tree[rt].minn = min(tree[rt << 1].minn , tree[rt << 1 | 1].minn); } ll query_min(ll L , ll R , ll rt) { if(L <= tree[rt].l && R >= tree[rt].r) return tree[rt].minn; push_down(rt); ll mid = tree[rt].l + tree[rt].r >> 1; ll res1 = (0x3f3f3f3f3f3f3f3fll) , res2 = (0x3f3f3f3f3f3f3f3fll); if(L <= mid) res1 = query_min(L , R , rt << 1); if(R > mid) res2 = query_min(L , R , rt << 1 | 1); return min(res1 , res2); } ll a[N] , pos[N] , sum[N]; int main() { int n ; cin >> n; for(int i = 1 ; i <= n ; i ++) { int x ; cin >> x; pos[x] = i; } for(int i = 1 ; i <= n ; i ++) cin >> a[i] , sum[i] = sum[i - 1] + a[i]; build(1 , n - 1 , 1 , sum); ll ans = min(a[1] , a[n]); for(int i = 1 ; i <= n - 1 ; i ++) { update_range(pos[i] , n - 1 , -a[pos[i]] , 1); update_range(1 , pos[i] - 1 , a[pos[i]] , 1); ans = min(ans , query_min(1 , n - 1 , 1)); } update_range(pos[1] , n - 1 , -a[pos[1]] , 1); update_range(1 , pos[i] - 1 , a[pos[1]] , 1); cout << ans << '\n'; return 0; }
来源:https://www.cnblogs.com/StarRoadTang/p/12249128.html