Educational Codeforces Round 81 (Rated for Div. 2)

余生颓废 提交于 2020-02-01 19:53:40

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;
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!