题目传送门

  神速的列车

  光速的列车

  声速的列车

题目大意

  给定一个长度为$n$的序列,$m$次询问区间$[l, r]$内相差最小的两个数的差的绝对值。

Solution 1 Mo's Algorithm & Linked List

  不会。。果断莫队

  当插入一个数的时候,如果用set维护前驱后继,然后结果是:$O((n + m)\sqrt{n}\log n)$,没救的,卡不过去。

  但是感觉原序列不会改变,使用set维护很浪费。

  考虑链表,链表上的每个数的后继是这个数的后继。

  考虑可以通过排序提前得到大小关系,不断从链表中删数就能得到它在剩下的区间内的前后继。如果询问在同一个块内直接暴力。否则把答案分成三部分。

  • 两个数都在当前块外,先从小到大加入剩下的所有数,从右到左删掉它们,再从左到右不断加入它们,然后就能得到所有前缀的答案。
  • 两个数都在当前块内,处理出所有后缀的答案,做法类似。
  • 一个在当前块内,一个在块外,移动右指针的时候让块内的所有数在链表内,查询的时候删掉再加入。

  然而这对我们解决问题没什么用,考虑对于按照一定顺序删除,然后逆序恢复这些被删除的点也是可行的。例如我依次删掉1,2,3。然后依次加入3,2,1.这中间我都能正确地找回前驱后继。

  这是一条优美的性质。

  由于答案不支持删除,考虑回滚莫队。

  当左端点在一块内的时候,首先从小到大(不是下标,是数值)建出链表。按照回滚莫队的方式,右端点需要从下一块块首开始向右扩展,因此从1开始,向后删掉链表中的元素,直到当前块块尾。然后从数组尾向前删除元素,直到链表被清空。然后从块尾的后一个位置开始向后加入元素,并记录当前区间的答案(因为你可以在链表中查询一个元素的前驱后继)。

  然后从块尾遍历到块首,依次加入元素,然后从后面删掉块外的元素。

  预处理部分就结束了,接着考虑处理询问。

  • 当询问区间在同一块内时。从块首删到询问的左端点(不包含它),然后从块尾删到询问的左端点。然后反着加入元素,边加边更新答案。最后加到块尾,最后将块左端删掉的一部分加回去。
  • 当询问区间跨过块端点时,从移动莫队右指针,加入元素。然后删掉当前块内的所有元素,从块尾加入元素,加到左端点,不断更新左半边产生的答案。那右半边本身的答案呢?之前记录了。然后两者取个最小值就是答案。

  确实好像有点繁琐,写起来还是不难受。详情可以看代码,语文不太好讲不清楚。

  因为要保证删掉的元素还能正确的找回来,所以需要进行辣么多次感觉是没用的删除。就因为这个常数比普通莫队大个2~3倍以上。然后codeforces上过了,floj上就T掉了。sad。

  不过感觉给个空限10M就能卡掉若干主席树做法。(果然最毒瘤的不是卡时间,而是卡空间。)

-->

Code

 /**
* Codeforces
* Problem#765F
* Accepted
* Time: 1309ms
* Memory: 9400k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define pii pair<int, int>
#define fi first
#define sc second const int cs = ;
const signed int inf = (signed) (~0u >> ); typedef class Query {
public:
int l, r, lid, rid, id, res; boolean operator < (Query b) const {
if (lid != b.lid) return lid < b.lid;
return r < b.r;
}
}Query; int n, m;
int *ar;
pii *cr;
int *pre, *suf, *L;
Query *qs; inline void init() {
scanf("%d", &n);
ar = new int[(n + )];
cr = new pii[(n + )];
pre = new int[(n + )];
suf = new int[(n + )];
L = new int[(n + )];
for (int i = ; i <= n; i++)
scanf("%d", ar + i), cr[i].fi = ar[i], cr[i].sc = i;
scanf("%d", &m);
qs = new Query[(m + )];
for (int i = ; i <= m; i++) {
scanf("%d%d", &qs[i].l, &qs[i].r);
qs[i].lid = (qs[i].l - ) / cs, qs[i].rid = (qs[i].r - ) / cs;
qs[i].id = i;
}
} void add(int p) {
suf[pre[p]] = p;
pre[suf[p]] = p;
} void remove(int p) {
suf[pre[p]] = suf[p];
pre[suf[p]] = pre[p];
} int update(int p) {
int rt = inf;
if (pre[p])
rt = ar[p] - ar[pre[p]];
if (suf[p] <= n)
rt = min(rt, ar[suf[p]] - ar[p]);
return rt;
} inline void solve() {
sort(cr + , cr + n + );
sort(qs + , qs + m + );
int c = ;
for (int sid = ; sid <= n / cs && c <= m; sid++) {
int mdzzr = min(cs * (sid + ), n), ls = , lr = cs * sid, ce = mdzzr; pre[] = , ls = ;
for (int i = ; i <= n; i++) {
pre[cr[i].sc] = ls;
suf[ls] = cr[i].sc;
ls = cr[i].sc;
}
suf[n + ] = n + , pre[n + ] = ls, suf[ls] = n + ; for (int i = ; i <= mdzzr; i++)
remove(i);
for (int i = n; i > mdzzr; i--)
remove(i);
L[mdzzr] = inf;
for (int i = mdzzr + ; i <= n; i++)
add(i), L[i] = min(L[i - ], update(i));
for (int i = mdzzr; i > lr; i--)
add(i);
for (int i = n; i > mdzzr; i--)
remove(i); for ( ; c <= m && qs[c].lid == sid; c++) {
int l = qs[c].l, r = qs[c].r;
if (qs[c].lid == qs[c].rid) {
for (int i = lr + ; i < l; i++)
remove(i);
for (int i = mdzzr; i >= l; i--)
remove(i);
int res = inf;
for (int i = l; i <= r; i++)
add(i), res = min(res, update(i));
for (int i = r + ; i <= mdzzr; i++)
add(i);
for (int i = l - ; i > lr; i--)
add(i);
qs[c].res = res;
} else {
while (mdzzr < r)
add(++mdzzr);
int res = inf;
for (int i = lr + ; i <= ce; i++)
remove(i);
for (int i = ce; i >= l; i--)
add(i), res = min(res, update(i));
for (int i = l - ; i > lr; i--)
add(i);
qs[c].res = min(res, L[r]);
}
}
} for (int i = ; i <= m; i++)
while (qs[i].id != i)
swap(qs[i], qs[qs[i].id]);
for (int i = ; i <= m; i++)
printf("%d\n", qs[i].res);
} int main() {
init();
solve();
return ;
}

Mo's Algorithm

Solution 2 Segment Tree

  考虑将询问离线,然后分别考虑大于等于位置$i$上的数和小于等于它的数产生的贡献。从左到右扫描数组。

  假设当前考虑以$r$为右端点的询问区间的答案。那么就要将$r$能产生的贡献计算出来。

  能产生贡献的位置是在$r$前大于等于$a_r$的一个递减数列(从后向前)。根据它的期望长度是$log_{n}$的,用个线段树区间取min,就可以水掉bzoj上面的某道题。

  于是cf上成功T掉了。

  加一个很强的剪枝:新找到的数和$a_r$的差必须小于上一个找到的差的一半。

  为什么是正确的呢?因为包含新找到的这个位置和$r$的区间一定包含上一个找到的数,但是显然新找到的数和上一个找到的数的差更优,会在另一次扫描中被统计。

  至于查找上一个在某个值域内最后出现的数的位置,再开一棵线段树。

Code

 /**
* Codeforces
* Problem#765F
* Accepted
* Time: 997ms
* Memory: 28600k
*/
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
typedef bool boolean; const signed int inf = (signed) (~0u >> ), Val = 1e9; typedef class SegTreeNode {
public:
int val;
SegTreeNode *l, *r; SegTreeNode() { } void pushUp() {
if (l)
val = l->val;
if (r)
val = max(val, r->val);
}
}SegTreeNode; SegTreeNode pool[];
SegTreeNode *top = pool; SegTreeNode* newnode() {
top->l = top->r = NULL;
return top++;
} typedef class SegTree {
public:
SegTreeNode* rt; SegTree():rt(NULL) { } void update(SegTreeNode*& p, int l, int r, int ql, int qr, int val) {
if (!p)
p = newnode(), p->val = inf;
if (l == ql && r == qr) {
p->val = min(p->val, val);
return ;
}
int mid = (l + r) >> ;
if (qr <= mid)
update(p->l, l, mid, ql, qr, val);
else if (ql > mid)
update(p->r, mid + , r, ql, qr, val);
else {
update(p->l, l, mid, ql, mid, val);
update(p->r, mid + , r, mid + , qr, val);
}
} int query(SegTreeNode *p, int l, int r, int idx) {
if (!p)
return inf;
if (l == r)
return p->val;
int mid = (l + r) >> , a = p->val, b = ;
if (idx <= mid)
b = query(p->l, l, mid, idx);
else
b = query(p->r, mid + , r, idx);
return min(a, b);
} void update(SegTreeNode* &p, int l, int r, int idx, int val) {
if (!p)
p = newnode(), p->val = -;
if (l == r) {
p->val = val;
return;
}
int mid = (l + r) >> ;
if (idx <= mid)
update(p->l, l, mid, idx, val);
else
update(p->r, mid + , r, idx, val);
p->pushUp();
} int query(SegTreeNode* p, int l, int r, int ql, int qr) {
if (!p)
return -;
if (l == ql && r == qr)
return p->val;
int mid = (l + r) >> ;
if (qr <= mid)
return query(p->l, l, mid, ql, qr);
if (ql > mid)
return query(p->r, mid + , r, ql, qr);
int a = query(p->l, l, mid, ql, mid);
int b = query(p->r, mid + , r, mid + , qr);
return max(a, b);
}
}SegTree; typedef class Query {
public:
int l, r, id, res; boolean operator < (Query b) const {
return r < b.r;
}
}Query; int n, m;
int *ar;
SegTree st, stv;
Query* qs; inline void init() {
scanf("%d", &n);
ar = new int[(n + )];
for (int i = ; i <= n; i++)
scanf("%d", ar + i);
scanf("%d", &m);
qs = new Query[(m + )];
for (int i = ; i <= m; i++) {
scanf("%d%d", &qs[i].l, &qs[i].r);
qs[i].id = i, qs[i].res = inf;
}
} inline void solve() {
sort(qs + , qs + m + );
for (int s = , c = ; s < ; s++) {
st.rt = stv.rt = NULL, top = pool, c = ;
for (int i = ; i <= n; i++) {
int rlim = Val, idx;
while (ar[i] <= rlim && (idx = stv.query(stv.rt, , Val, ar[i], rlim)) != -) {
st.update(st.rt, , n, , idx, ar[idx] - ar[i]);
rlim = ((ar[idx] + ar[i] - ) >> );
}
stv.update(stv.rt, , Val, ar[i], i);
for ( ; c <= m && qs[c].r == i; c++)
qs[c].res = min(qs[c].res, st.query(st.rt, , n, qs[c].l));
}
for (int i = ; i <= n; i++)
ar[i] = Val - ar[i];
} for (int i = ; i <= m; i++)
while (qs[i].id != i)
swap(qs[i], qs[qs[i].id]);
for (int i = ; i <= m; i++)
printf("%d ", qs[i].res);
} int main() {
init();
solve();
return ;
}

Codeforces 765F Souvenirs - 莫队算法 - 链表 - 线段树的更多相关文章

  1. bzoj 4358 Permu - 莫队算法 - 链表

    题目传送门 需要高级权限的传送门 题目大意 给定一个全排列,询问一个区间内的值域连续的一段的长度的最大值. 考虑使用莫队算法. 每次插入一个数$x$,对值域的影响可以分成4种情况: $x - 1$, ...

  2. CodeForces - 220B 离散化+莫队算法

    莫队算法链接:传送门 题意: 有n个数,m个区间.问区间内有多少个x,x满足x的个数等于x的值的个数(如果x是3,区间内要存在3个3). 题解: 因为a[i]太大,所以要离散化一下,但是不能用map容 ...

  3. Powerful array CodeForces - 86D (莫队算法)

    An array of positive integers a1, a2, ..., an is given. Let us consider its arbitrary subarray al, a ...

  4. BZOJ.4540.[HNOI2016]序列(莫队/前缀和/线段树 单调栈 RMQ)

    BZOJ 洛谷 ST表的一二维顺序一定要改过来. 改了就rank1了哈哈哈哈.自带小常数没办法. \(Description\) 给定长为\(n\)的序列\(A_i\).\(q\)次询问,每次给定\( ...

  5. [莫队算法 线段树 斐波那契 暴力] Codeforces 633H Fibonacci-ish II

    题目大意:给出一个长度为n的数列a. 对于一个询问lj和rj.将a[lj]到a[rj]从小到大排序后并去重.设得到的新数列为b,长度为k,求F1*b1+F2*b2+F3*b3+...+Fk*bk.当中 ...

  6. bzoj 3809 Gty的二逼妹子序列(莫队算法,块状链表)

    [题意] 回答若干个询问,(l,r,a,b):区间[l,r]内权值在[a,b]的数有多少[种]. [思路] 考虑使用块状链表实现莫队算法中的插入与删除. 因为权值处于1..n之间,所以我们可以建一个基 ...

  7. Codeforces Round #340 (Div. 2) E. XOR and Favorite Number 莫队算法

    E. XOR and Favorite Number 题目连接: http://www.codeforces.com/contest/617/problem/E Descriptionww.co Bo ...

  8. Codeforces Round #340 (Div. 2) E. XOR and Favorite Number 【莫队算法 + 异或和前缀和的巧妙】

    任意门:http://codeforces.com/problemset/problem/617/E E. XOR and Favorite Number time limit per test 4 ...

  9. 莫队算法初识~~CodeForces - 617E

    E. XOR and Favorite Number time limit per test 4 seconds memory limit per test 256 megabytes input s ...

随机推荐

  1. js重定向跳转页面

    重定向方式: 1>  window.location ='www.baidu.com';    window.location='/';  window.location='/logout/'; ...

  2. Appium遇到的问题二(持续更新....)

    Python版: 1.运行Appium遇到的错误:此问题是由于JDK版本要在1.7及以上. Android开发要求. A new session could not be created. C:\Py ...

  3. VC6.0 error LNK2001: unresolved external symbol __imp__ntohl@4

    --------------------Configuration: oxToint1 - Win32 Debug-------------------- Linking... main.obj : ...

  4. why big data

    很多人都知道大数据很火,就业很好,薪资很高,想往大数据方向发展.但该学哪些技术,学习路线是什么样的呢?用不用参加大数据培训呢?如果自己很迷茫,为了这些原因想往大数据方向发展,也可以,那么大讲台老师就想 ...

  5. Android -- 仿淘宝广告条滚动

    1,在赶项目的时候我们经常会实现下面这个功能,及添加滚动条广告广播,先看一下淘宝的效果 2,这次实现效果主要使用Android自带的ViewFlipper控件,先来看一下我们的它的基本属性和基本方法吧 ...

  6. ling join 报错The specified LINQ expression contains references to queries that are associated with different cont

    The specified LINQ expression contains references to queries that are associated with different cont ...

  7. Spark学习之路 (八)SparkCore的调优之开发调优

    摘抄自:https://tech.meituan.com/spark-tuning-basic.html 前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark ...

  8. win10安装mongodb-win32-x86_64-2008plus-ssl-3.4.10-signed

    1.下载mongodb在windows下的安装文件 首先去官网https://www.mongodb.com/download-center?jmp=nav#community下载安装文件.mongo ...

  9. localstorage跨域解决方案

    localstorage也存在 跨域的问题, [解决思路如下] 在A域和B域下引入C域,所有的读写都由C域来完成,本地数据存在C域下; 因此 A哉和B域的页面必定要引入C域的页面; 当然C域最好是主域 ...

  10. 校正PHP服务器时间不准的问题

    关于怎样解决PHP服务器时间不准的问题,得针对不同的情况进行不同的处理. 下面是经常遇到的情况,及应对办法. 1.PHP服务器时区不对,使用下面代码修正: <?php $timezone = & ...