Souvenirs

我们将询问离线, 我们从左往右加元素, 如果当前的位置为 i ,用一棵线段树保存区间[x, i]的答案,

每次更新完, 遍历R位于 i 的询问更新答案。

我们先考虑最暴力的做法, 我们先找到位于 i 前面第一个 j, a[ j ] > a[ i ], 那么x 属于 [ 1, j ]的答案

就会被a[ j ] - [ i ] 更新一下。 然后下一个找在 j 前面第一个 k, a[ k ] >= a[ i ] && a[ k ] < a[ j ], 这个过程

可以用一个主席树来维护。 但是这样的做法肯定会T掉, 实际上我们多了很多没有必要的更新。

我们找 k 的时候肯定要满足这个不等式, a[ k ] - a[ i ] < a[ j ] - a[ k ]    ->   a[ k ] < (a[ i ] + a[ j ] ) / 2, 这样

就只要重复log(max)次就能完成。

为什么这个不等式成立呢, 因为, a[ j ] - a[ k ] 这个值在 j 加进去的时候就更新了一次,没必要找比这个值大的。

#include<bits/stdc++.h>
#define LL long long
#define fi first
#define se second
#define mk make_pair
#define PLL pair<LL, LL>
#define PLI pair<LL, int>
#define PII pair<int, int>
#define SZ(x) ((int)x.size())
#define ull unsigned long long using namespace std; const int N = 1e5 + ;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = ;
const double eps = 1e-;
const double PI = acos(-); int n, m, a[N], ans[N * ];
vector<PII> qus[N];
vector<int> oo; #define lson l, mid , rt << 1
#define rson mid + 1, r, rt << 1 | 1 namespace SGT1 {
int a[N << ];
void build(int l, int r, int rt) {
a[rt] = inf;
if(l == r) return;
int mid = l + r >> ;
build(lson); build(rson);
}
void update(int L, int R, int val, int l, int r, int rt) {
if(l >= L && r <= R) {
a[rt] = min(a[rt], val);
return;
}
int mid = l + r >> ;
if(L <= mid) update(L, R, val, lson);
if(R > mid) update(L, R, val, rson);
}
int query(int p, int l, int r, int rt) {
if(l == r) return a[rt];
int mid = l + r >> ;
if(p <= mid) return min(query(p, lson), a[rt]);
else return min(query(p, rson), a[rt]);
}
} namespace SGT2 {
int tot = , Rt[N];
struct Node {
int mx, ls, rs;
} a[N * ];
void init() {
tot = ;
}
void update(int p, int val, int l, int r, int& x, int y) {
x = ++tot; a[x] = a[y]; a[x].mx = max(a[x].mx, val);
if(l == r) return;
int mid = l + r >> ;
if(p <= oo[mid - ]) update(p, val, l, mid, a[x].ls, a[y].ls);
else update(p, val, mid + , r, a[x].rs, a[y].rs);
}
int query(int L, int R, int l, int r, int x) {
if(L > oo[r - ] || R < oo[l - ]) return ;
if(oo[l - ] >= L && oo[r - ] <= R) return a[x].mx;
int mid = l + r >> ;
if(R <= oo[mid - ]) return query(L, R, l, mid, a[x].ls);
else if(L > oo[mid - ]) return query(L, R, mid + , r, a[x].rs);
else return max(query(L, R, l, mid, a[x].ls), query(L, R, mid + , r, a[x].rs));
}
} void solve() {
oo.clear();
SGT1::build(, n, );
SGT2::init();
for(int i = ; i <= n; i++) oo.push_back(a[i]);
sort(oo.begin(), oo.end());
oo.erase(unique(oo.begin(), oo.end()), oo.end());
for(int i = ; i <= n; i++)
SGT2::update(a[i], i, , SZ(oo), SGT2::Rt[i], SGT2::Rt[i - ]);
for(int i = ; i <= n; i++) {
int cnt = ;
int now = SGT2::query(a[i], inf, , SZ(oo), SGT2::Rt[i - ]);
while(now) {
SGT1::update(, now, a[now] - a[i], , n, );
if(a[now] == a[i]) break;
int nxt = SGT2::query(a[i], a[i] + a[now] >> , , SZ(oo), SGT2::Rt[now - ]);
now = nxt;
}
for(auto& q : qus[i])
ans[q.se] = min(ans[q.se], SGT1::query(q.fi, , n, ));
}
} int main() {
memset(ans, inf, sizeof(ans));
scanf("%d", &n);
for(int i = ; i <= n; i++) scanf("%d", &a[i]);
scanf("%d", &m);
for(int i = ; i <= m; i++) {
int L, R; scanf("%d%d", &L, &R);
qus[R].push_back(mk(L, i));
}
solve();
for(int i = ; i <= n; i++) a[i] = -a[i];
solve();
for(int i = ; i <= m; i++) printf("%d\n", ans[i]);
return ;
} /*
*/

Codeforces 765F Souvenirs 线段树 + 主席树 (看题解)的更多相关文章

  1. 线段树简单入门 (含普通线段树, zkw线段树, 主席树)

    线段树简单入门 递归版线段树 线段树的定义 线段树, 顾名思义, 就是每个节点表示一个区间. 线段树通常维护一些区间的值, 例如区间和. 比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和 ...

  2. Codeforces 765F Souvenirs - 莫队算法 - 链表 - 线段树

    题目传送门 神速的列车 光速的列车 声速的列车 题目大意 给定一个长度为$n$的序列,$m$次询问区间$[l, r]$内相差最小的两个数的差的绝对值. Solution 1 Mo's Algorith ...

  3. Codeforces.765F.Souvenirs(主席树)

    题目链接 看题解觉得非常眼熟,总感觉做过非常非常类似的题啊,就是想不起来=v=. 似乎是这道...也好像不是. \(Description\) 给定长为\(n\)的序列\(A_i\).\(m\)次询问 ...

  4. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

  5. 小结:线段树 & 主席树 & 树状数组

    概要: 就是用来维护区间信息,然后各种秀智商游戏. 技巧及注意: 一定要注意标记的下放的顺序及影响!考虑是否有叠加或相互影响的可能! 和平衡树相同,在操作每一个节点时,必须保证祖先的tag已经完全下放 ...

  6. [学习笔记] 可持久化线段树&主席树

    众所周知,线段树是一个非常好用也好写的数据结构, 因此,我们今天的前置技能:线段树. 然而,可持久化到底是什么东西? 别急,我们一步一步来... step 1 首先,一道简化的模型: 给定一个长度为\ ...

  7. 权值线段树&&可持久化线段树&&主席树

    权值线段树 顾名思义,就是以权值为下标建立的线段树. 现在让我们来考虑考虑上面那句话的产生的三个小问题: 1. 如果说权值作为下标了,那这颗线段树里存什么呢? ----- 这颗线段树中, 记录每个值出 ...

  8. 学习笔记--函数式线段树(主席树)(动态维护第K极值(树状数组套主席树))

    函数式线段树..资瓷 区间第K极值查询 似乎不过似乎划分树的效率更优于它,但是如果主席树套树状数组后,可以处理动态的第K极值.即资瓷插入删除,划分树则不同- 那么原理也比较易懂: 建造一棵线段树(权值 ...

  9. UOJ#218. 【UNR #1】火车管理 线段树 主席树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ218.html 题解 如果我们可以知道每次弹出栈之后新的栈顶是什么,那么我们就可以在一棵区间覆盖.区间求和 ...

随机推荐

  1. ansible笔记(2):清单配置详解

    上一篇文章介绍了ansible的基本概念,以及相关的基础配置,我们已经知道,如果想要管理受管主机,则需要将受管主机添加到ansible的管理清单中,当安装ansible以后,会提供一个默认的管理清单, ...

  2. php7静态方法的链式调用

    2018-1-11 20:25:48 星期四 情景: 以前想要链式调用必须先 new 一个对象, 然后 $obj->aa()->bb()... 现在PHP7 (php7.0.13  php ...

  3. mybatis:访问静态变量或方法

    访问方法: <if test="@com.csget.constant.ConstantApp@getUser('mobile')== 'kf'"> <![CDA ...

  4. tornado的异步效果

    第一种方式: import tornado.ioloop import tornado.web from tornado import gen from tornado.concurrent impo ...

  5. HashMap遍历的两种方式,推荐使用entrySet()

    第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) {    ...

  6. 20)django-session使用

    一:目录 1)session原理 2)cookie与session对比 3)session配置 4)session使用 5)示例 二:session原理 Django的Session机制会向请求的浏览 ...

  7. grep匹配某个次出现的次数

    cat file | grep  -c 'xxx'  统计xxx在file中出现的行数 cat file | grep  -o 'xxx'  统计xxx在file中出现的次数

  8. Happiness

    1575: Happiness 时间限制: 1 Sec  内存限制: 1280 MB 题目描述 Chicken brother is very happy today, because he atta ...

  9. 微信小程序 如何获取用户code

    1.首先需要获取code 使用 wx.login({ success: function(res) { console.log(res);//这里的返回值里面便包含code }, fail: func ...

  10. ActiveMQ使用的设计模式

    注:接收不需要连接池,而发送需要连接池,是因为,接收在启动项目时就要注册监听,数目是固定的,而发送则会随着时间数目不断在变动,需要连接池,性能更优. 重点代码: private static void ...