CF573E Bear and Bowling 贪心、分块、凸包

传送门


题解搬运工++

先证明一个贪心做法的正确性:做以下操作若干次,每一次考虑选择没有被选到答案序列中的数加入到答案序列中对答案的贡献,设第\(i\)个位置的贡献为\(V_i\),如果最大的贡献小于0则退出,否则选择其中贡献最大的加入答案序列中。

首先一个引理:在上述贪心策略下,如果\(a_i\)>\(a_j\)\(i\)<\(j\),则选\(i\)之前不可能选\(j\)

证明考虑归纳:\(i,j\)中间不存在被选中的元素时是平凡的,如果\(i,j\)中间存在\(p\)个选中的元素,若\(V_i\)<\(V_j\)则一定在\([i,j]\)之间存在至少一个\(x\)满足\(a_x\)<\(a_j\),此时没有选\(j\)所以不可能选择\(x\),与假设不符,QED。

接下来假设贪心策略不正确,即在选择了集合\(A\)之后将下标为\(x\)的位置选中,但是最优的答案是选择集合\(A+B\),其中\(x \not\in B\)。那么考虑:

1、如果\(B\)中存在位置在x左边,考虑在\(x\)左边的最右位置\(y\),那么此时有\(a_y \leq a_x , V_x \geq V_y\)。此时加入集合\(B\)中的其他元素考虑\(V_x,V_y\)的变化,那么在\(x\)右边的元素对\(V_x,V_y\)的贡献一样,在\(y\)左边的元素对\(V_x,V_y\)的贡献是\(a_x,a_y\),而\(x,y\)中间没有在\(B\)中的元素,所以可以发现在其他元素加入之后\(V_x \geq V_y\),所以将\(B\)\(y\)换成\(x\)结果不劣。

2、如果\(B\)中只有在\(x\)右边的元素,考虑在\(x\)右边的最左位置\(y\),那么\(B\)集合其他的元素对\(V_x,V_y\)的贡献是一样的,所以把\(y\)换成\(x\)也不会更劣。

故上述假设不成立,贪心正确性证毕。

那么我们考虑选择了一个元素之后对答案的影响。假如选择了位置\(x\),那么当\(y < x\)时,\(V_y += x\);当\(y > x\)时,\(V_y += a_y\)。那么每一个时刻\(V_y\)都可以表示成\(ka_y+b\)的形式,用分块维护凸包即可。

#include<cstdio>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<bitset>
#include<random>
#include<unistd.h>
//This code is written by Itst
using namespace std;

#define int long long
#define PII pair < int , int >
#define ld long double
const int _ = 1e5 + 7 , T = sqrt(_) + 5;
int arr[_] , add[_] , mrk[T] , id[_] , N;

ld sect(PII p , PII q){return 1.0 * (q.second - p.second) / (p.first - q.first);}
int calc(PII p , int q){return p.first * q + p.second;}

struct Hull{
    deque < PII > q; int mrk;

    int get(){while(q.size() >= 2 && calc(q[0] , mrk) <= calc(q[1] , mrk)) q.pop_front(); return calc(q[0] , mrk);}
    void up(){++mrk;}
    
    void rebuild(int bl){
        q.clear(); mrk = 0;
        for(int i = bl * T ; i < N && i < (bl + 1) * T ; ++i){
            PII now(arr[id[i]] , add[id[i]]);
            if(q.size() && q.back().first == now.first)
                if(q.back().second > now.second) continue;
                else q.pop_back();
            while(q.size() >= 2 && sect(q[q.size() - 2] , now) < sect(q[q.size() - 2] , q[q.size() - 1])) q.pop_back();
            q.push_back(now);
        }
    }
}now[T];

void down(int bl){
    for(int i = T * bl ; i < N && i < (bl + 1) * T ; ++i)
        add[i] += now[bl].mrk * arr[i] + mrk[bl];
    mrk[bl] = 0;
}

signed main(){
    scanf("%lld" , &N);
    for(int i = 0 ; i < N ; ++i){scanf("%lld" , &arr[id[i] = i]); add[i] = arr[i];}
    for(int i = 0 ; i < N ; i += T){
        sort(id + i , id + min(T + i , N) , [&](int p , int q){return arr[p] < arr[q];});
        now[i / T].rebuild(i / T);
    }
    int sum = 0;
    while(1){
        int mx = -1e18 , id = -1;
        for(int i = 0 ; i < N / T + (bool)(N % T) ; ++i) if(mx < now[i].get() + mrk[i]){mx = now[i].get() + mrk[i]; id = i;}
        if(mx < 0) break;
        sum += mx; down(id);
        for(int i = T * id ; i < (id + 1) * T ; ++i)
            if(add[i] == mx){
                for(int j = 0 ; j < id ; ++j) mrk[j] += arr[i];
                for(int j = id + 1 ; j < N / T + (bool)(N % T) ; ++j) now[j].up();
                for(int j = T * id ; j < i ; ++j) add[j] += arr[i];
                add[i] = -1e15;
                for(int j = i + 1 ; j < N && j < (id + 1) * T ; ++j) add[j] += arr[j];
                now[id].rebuild(id);
                break;
            }
    }
    printf("%lld\n" , sum); return 0;
}

转载于:https://www.cnblogs.com/Itst/p/11604866.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值