BZOJ1805: [Ioi2007]Sail 船帆 [思维题,线段树优化贪心]
题目描述见链接 .
首先旗杆的顺序是对答案没有影响的, 我们只需关注每一行放置了多少旗帜,
于是可以先按照旗杆的高度排序, 然后考虑从左向右按顺序安插旗子,
对当前的旗杆 , 为了使得它对答案贡献最小, 贪心 地选取 高度内旗子个数前 小的高度放置旗子,
这样能保证答案最优 .
为了保证答案最优, 放旗子少的高度在后面肯定会放置旗子, 而先放置和后放置对答案是不造成影响的 .
题目就转化为了: 给出一个数组 , 有 次操作, 第 次操作是将 的前 项的前 小项加 .
可以证明保持 单调递减可以得到最优方案,
假设已有一个最优方案, 对于 且 的情况, 可以将 的旗子数与 的旗子数反转, 从而保证 的递减性 .
所以要进行操作的元素就可以转化成一个连续的区间: , 直接使用 线段树 进行区间加减
仍然不行, 设 , 若 , 直接进行区间加会破坏单调性, 于是需要找到 的最小 , 和 的最大 .
- 若 , 则直接对 区间 进行操作 .
- 若 , 则分别对区间 进行操作 .
- 找 时先找到 , 然后在线段树查询时尽量往左子树 ,
具体地说, 若左子树的最小值小于等于 , 说明左子树可能存在 , 否则向右走 . - 找 时先找到 , 然后在线段树查询时尽量往右子树 ,
具体地说, 若右子树的最大值小于等于 , 说明右子树可能存在 , 否则向左走 .
#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)
*/