BZOJ1805: [Ioi2007]Sail 船帆 [思维题,线段树优化贪心]

S a i l Sail Sail

题目描述见链接 .


正 解 部 分 \color{red}{正解部分}

首先旗杆的顺序是对答案没有影响的, 我们只需关注每一行放置了多少旗帜,
于是可以先按照旗杆的高度排序, 然后考虑从左向右按顺序安插旗子,
对当前的旗杆 i i i, 为了使得它对答案贡献最小, 贪心 地选取 [ 1 , H i ] [1, H_i] [1,Hi] 高度内旗子个数前 K i K_i Ki 小的高度放置旗子,
这样能保证答案最优 .

证 明 : 证明: :
为了保证答案最优, 放旗子少的高度在后面肯定会放置旗子, 而先放置和后放置对答案是不造成影响的 .

题目就转化为了: 给出一个数组 A [ ] A[] A[], 有 N N N 次操作, 第 i i i 次操作是将 A [ ] A[] A[] 的前 H i H_i Hi 项的前 K i K_i Ki 小项加 1 1 1 .

可以证明保持 A [ ] A[] A[] 单调递减可以得到最优方案,

证 明 : 证明: :
假设已有一个最优方案, 对于 H 1 > H 2 H_1 > H_2 H1>H2 A [ H 1 ] > A [ H 2 ] A[H_1] > A[H_2] A[H1]>A[H2] 的情况, 可以将 H 1 H_1 H1 的旗子数与 H 2 H_2 H2 的旗子数反转, 从而保证 A [ ] A[] A[] 的递减性 .

所以要进行操作的元素就可以转化成一个连续的区间: [ H i − K i + 1 , H i ] [H_i-K_i+1, H_i] [HiKi+1,Hi], 直接使用 线段树 进行区间加减 ? ? ?

仍然不行, 设 L = H i − K i + 1 , R = H i L = H_i-K_i+1, R = H_i L=HiKi+1,R=Hi, 若 A [ L − 1 ] = = A [ L ] A[L-1] == A[L] A[L1]==A[L], 直接进行区间加会破坏单调性, 于是需要找到 A [ j ] = = A [ L ] A[j] == A[L] A[j]==A[L] 的最小 j j j, 和 A [ k ] = = A [ L ] A[k] == A[L] A[k]==A[L] 的最大 k k k .

  • k ≥ R k \geq R kR, 则直接对 区间 [ j , j + K i − 1 ] [j, j+K_i-1] [j,j+Ki1] 进行操作 .
  • k < R k < R k<R, 则分别对区间 [ j , j + ( R − k ) ] ,   [ k + 1 , R ] [j, j+(R-k)],\ [k+1, R] [j,j+(Rk)], [k+1,R] 进行操作 .

实 现 部 分 \color{red}{实现部分}

  • L L L 时先找到 A [ H i ] A[H_i] A[Hi], 然后在线段树查询时尽量往左子树 " 靠 " "靠" "",
    具体地说, 若左子树的最小值小于等于 A [ H i ] A[H_i] A[Hi], 说明左子树可能存在 A [ H i ] A[H_i] A[Hi], 否则向右走 .
  • R R R 时先找到 A [ H i ] A[H_i] A[Hi], 然后在线段树查询时尽量往右子树 " 靠 " "靠" "",
    具体地说, 若右子树的最大值小于等于 A [ H i ] A[H_i] A[Hi], 说明右子树可能存在 A [ H i ] A[H_i] A[Hi], 否则向左走 .
#include<bits/stdc++.h>
#define reg register
typedef long long ll;

const int maxn = 100005;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

int N;
int Max_h;

struct Mast{ int h, k; } A[maxn];

bool cmp_1(Mast a, Mast b){ return a.h < b.h; }

struct Seg_tree{ 
        struct Node{ int l, r, max_v, min_v, tag; } T[maxn<<2];
        void Build(int k, int l, int r){
                T[k].l = l, T[k].r = r;
                if(l == r) return ; int mid = l+r >> 1;
                Build(k<<1, l, mid), Build(k<<1|1, mid+1, r);
        }
        void Push_down(int k){
                T[k<<1].tag += T[k].tag, T[k<<1].min_v += T[k].tag, T[k<<1].max_v += T[k].tag;
                T[k<<1|1].tag += T[k].tag, T[k<<1|1].min_v += T[k].tag, T[k<<1|1].max_v += T[k].tag;
                T[k].tag = 0;
        }
        int Query_val(int k, int aim_id){
                int l = T[k].l, r = T[k].r; 
                if(l == r) return T[k].min_v; if(T[k].tag) Push_down(k);
                int mid = l+r >> 1;
                if(aim_id <= mid) return Query_val(k<<1, aim_id);
                return Query_val(k<<1|1, aim_id);
        }
        int Query_L(int k, int aim_v){
                int l = T[k].l, r = T[k].r;
                if(l == r) return l; if(T[k].tag) Push_down(k);
                if(T[k<<1].min_v <= aim_v) return Query_L(k<<1, aim_v);
                return Query_L(k<<1|1, aim_v);
        }
        int Query_R(int k, int aim_v){
                int l = T[k].l, r = T[k].r;
                if(l == r) return l; if(T[k].tag) Push_down(k);
                if(T[k<<1|1].max_v >= aim_v) return Query_R(k<<1|1, aim_v);
                return Query_R(k<<1, aim_v);
        }
        void Modify(int k, int ql, int qr, int add){
                int l = T[k].l, r = T[k].r;
                if(ql <= l && r <= qr){ T[k].tag += add, T[k].max_v += add, T[k].min_v += add; return ; }
                if(T[k].tag) Push_down(k);
                int mid = l+r >> 1;
                if(ql <= mid) Modify(k<<1, ql, qr, add);
                if(qr > mid) Modify(k<<1|1, ql, qr, add);
                T[k].min_v = std::min(T[k<<1].min_v, T[k<<1|1].min_v);
                T[k].max_v = std::max(T[k<<1].max_v, T[k<<1|1].max_v);
        }
        ll calc_ans(int k){
                int l = T[k].l, r = T[k].r;
                if(l == r) return 1ll*T[k].min_v*(T[k].min_v-1)/2;
                if(T[k].tag) Push_down(k);
                return calc_ans(k<<1) + calc_ans(k<<1|1);
        }
} seg_t;

int main(){
        N = read();
        for(reg int i = 1; i <= N; i ++) Max_h = std::max(Max_h, A[i].h=read()), A[i].k = read();
        std::sort(A+1, A+N+1, cmp_1);
        seg_t.Build(1, 1, Max_h);
        for(reg int i = 1; i <= N; i ++){
                int tmp = seg_t.Query_val(1, A[i].h-A[i].k+1);
                int L = seg_t.Query_L(1, tmp), R = seg_t.Query_R(1, tmp);
                if(R >= A[i].h) seg_t.Modify(1, L, L+A[i].k-1, 1);
                else{
                        int len = A[i].k - (A[i].h - R);
                        seg_t.Modify(1, R+1, A[i].h, 1), seg_t.Modify(1, L, L+len-1, 1);
                }
        }
        printf("%lld\n", seg_t.calc_ans(1));
        return 0;
}
/*
1. calc_ans() leak of push_down;
2. Query_val() swap(mid, aim_v)
*/

官方题解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值