CF765F Souvenirs()

可以发现这个问题类似询问区间逆序对(都和排序相关,都和点对相关),于是我们可以顺着 P5046 的方法去想。

分块。把询问分成询问端点同块与询问端点异块的情况。

询问端点同块

此时可以考虑处理出每个块排序后的数组。沿着排序后的数组,按大小顺序地“取出”\(l\sim r\) 的这一段区间,然后统计。

询问端点异块

参照 P5046 分成如下几部分:

  • 散块内部与散块之间
  • 整块与散块之间
  • 整块内部与整块之间

散块内部与散块之间

与询问端点同块情况类似,取出两个散块分别得排序后的状态,归并起来,然后统计,我直接用了 。

STL

整块与散块之间

预处理两个数组:

  • \(D1_{i,j}\) 代表 \(i\) 所在块以 \(i\) 结尾的前缀 与 \(i\) 所在块的下一块至第 \(j\) 块 这两个区间的答案。
  • \(D2_{i,j}\) 代表 \(i\) 所在块以 \(i\) 开头的后缀 与 \(i\) 所在块的上一块至第 \(j\) 块 这两个区间的答案。

那如何预处理呢?可以考虑先处理 一个位置对一个块的答案,再前缀和变成 一个前缀/后缀对一个块的答案,再前缀和成 一个前缀/后缀对一些块的答案

一个位置对一个块的答案 怎么算呢?可以先钦定两个块 \(a,b\),然后用双指针算块 \(a\) 内的所有数分别与块 \(b\) 的答案。

具体算法为:块 \(a\) 内的元素从小往大算(别忘了我们之前给每个块排过序),然后定义一个指针 \(p\),\(p\) 一开始位于 \(b\) 的块首,然后随着 \(a\) 内计算的位置从前往后,\(p\) 也随着变大,期间满足排序后的数组内 \(a_p<a_i\)。

这两个数组没有相交的地方,考虑将其合并。

整块内部与整块之间

考虑预处理 \(S_{i,j}\) 代表块 \(i\) 开头至块 \(j\) 结尾之间的答案。

可以先处理 \(S_{i,i}\) 代表一个块的答案,然后利用 \(D\) 数组扩展到多个块的答案。

代码

感觉写的还算简单,100 行不到。

#include<stdio.h>
#include<math.h>
#include<algorithm>

const int maxn = 1e5 + 5;
const int sqr = 325;
const int inf = 1e9 + 5;

int n,m,a[maxn],B,C;
int D[maxn][sqr],S[sqr][sqr];
int st[sqr],ed[sqr],bl[maxn];
int tmp[maxn];

struct value{
	int i,x;
	bool operator<(const value& v) const {
		return x < v.x;
	}
} b[maxn];

int qry0(rint l,rint r){
	int ans = inf,len = 0;
	rep(i,st[bl[l]],ed[bl[l]])
		if(l <= b[i].i && b[i].i <= r) tmp[++len] = b[i].x;
	rep(i,2,len) chkmin(ans,tmp[i] - tmp[i - 1]);
	return ans;
}

int qry1(rint l1,rint r1,rint l2,rint r2){
	int len = 0,p = r1 - l1 + 1,ans = inf;
	rep(i,st[bl[l1]],ed[bl[l1]])
		if(l1 <= b[i].i && b[i].i <= r1) tmp[++len] = b[i].x;
	rep(i,st[bl[l2]],ed[bl[l2]])
		if(l2 <= b[i].i && b[i].i <= r2) tmp[++len] = b[i].x;
	std::inplace_merge(tmp + 1,tmp + p + 1,tmp + len + 1);
	rep(i,2,len) chkmin(ans,tmp[i] - tmp[i - 1]); 
	return ans;
}

void init(){
	B = sqrt(n); C  = (n - 1) / B + 1;
	rep(i,1,C){
		st[i] = (i - 1) * B + 1;
		ed[i] = i == C ? n : i * B;
		rep(j,st[i],ed[i]) bl[j] = i,b[j].i = j,b[j].x = a[j];
		std::sort(b + st[i],b + ed[i] + 1);
	}
	rep(i,1,C){
		rep(j,1,C) if(i ^ j){
			rint p = st[j];
			rep(k,st[i],ed[i]){
				while(p < ed[j] && b[p + 1].x < b[k].x) ++p;
				D[b[k].i][j] = abs2(b[k].x - b[p].x);
				if(p < ed[j]) chkmin(D[b[k].i][j],abs2(b[k].x - b[p + 1].x));
			}
		}
		rep(j,st[i],ed[i]) rep(k,i + 2,C) chkmin(D[j][k],D[j][k - 1]);
		rep(j,st[i],ed[i]) Rep(k,i - 2,0) chkmin(D[j][k],D[j][k + 1]);
		rep(k,1,i - 1) rep(j,st[i] + 1,ed[i]) chkmin(D[j][k],D[j - 1][k]);
		rep(k,i + 1,C) Rep(j,ed[i] - 1,st[i]) chkmin(D[j][k],D[j + 1][k]);
		S[i][i] = qry0(st[i],ed[i]);
	}
	rep(i,1,C) rep(j,i + 1,C) S[i][j] = min2(min2(S[i][j - 1],S[j][j]),D[ed[j]][i]);
}	

int qry(rint l,rint r){
	if(bl[l] == bl[r]) return qry0(l,r);
	int ans = qry1(l,ed[bl[l]],st[bl[r]],r);
	if(bl[l] + 1 <= bl[r] - 1){	
		chkmin(ans,S[bl[l] + 1][bl[r] - 1]);
		chkmin(ans,min2(D[r][bl[l] + 1],D[l][bl[r] - 1]));
	}
	return ans;
}

int main(){
	read(n); rep(i,1,n) read(a[i]);
	init(); read(m);
	while(m--){
		int l,r; read(l,r);
		print(qry(l,r),'\n');
	}
	return flush(),0;
}
————————

可以发现这个问题类似询问区间逆序对(都和排序相关,都和点对相关),于是我们可以顺着 P5046 的方法去想。

分块。把询问分成询问端点同块与询问端点异块的情况。

询问端点同块

此时可以考虑处理出每个块排序后的数组。沿着排序后的数组,按大小顺序地“取出”\(l\sim r\) 的这一段区间,然后统计。

询问端点异块

参照 P5046 分成如下几部分:

  • 散块内部与散块之间
  • 整块与散块之间
  • 整块内部与整块之间

散块内部与散块之间

与询问端点同块情况类似,取出两个散块分别得排序后的状态,归并起来,然后统计,我直接用了 。

STL

整块与散块之间

预处理两个数组:

  • \(D1_{i,j}\) 代表 \(i\) 所在块以 \(i\) 结尾的前缀 与 \(i\) 所在块的下一块至第 \(j\) 块 这两个区间的答案。
  • \(D2_{i,j}\) 代表 \(i\) 所在块以 \(i\) 开头的后缀 与 \(i\) 所在块的上一块至第 \(j\) 块 这两个区间的答案。

那如何预处理呢?可以考虑先处理 一个位置对一个块的答案,再前缀和变成 一个前缀/后缀对一个块的答案,再前缀和成 一个前缀/后缀对一些块的答案

一个位置对一个块的答案 怎么算呢?可以先钦定两个块 \(a,b\),然后用双指针算块 \(a\) 内的所有数分别与块 \(b\) 的答案。

具体算法为:块 \(a\) 内的元素从小往大算(别忘了我们之前给每个块排过序),然后定义一个指针 \(p\),\(p\) 一开始位于 \(b\) 的块首,然后随着 \(a\) 内计算的位置从前往后,\(p\) 也随着变大,期间满足排序后的数组内 \(a_p<a_i\)。

这两个数组没有相交的地方,考虑将其合并。

整块内部与整块之间

考虑预处理 \(S_{i,j}\) 代表块 \(i\) 开头至块 \(j\) 结尾之间的答案。

可以先处理 \(S_{i,i}\) 代表一个块的答案,然后利用 \(D\) 数组扩展到多个块的答案。

代码

感觉写的还算简单,100 行不到。

#include<stdio.h>
#include<math.h>
#include<algorithm>

const int maxn = 1e5 + 5;
const int sqr = 325;
const int inf = 1e9 + 5;

int n,m,a[maxn],B,C;
int D[maxn][sqr],S[sqr][sqr];
int st[sqr],ed[sqr],bl[maxn];
int tmp[maxn];

struct value{
	int i,x;
	bool operator<(const value& v) const {
		return x < v.x;
	}
} b[maxn];

int qry0(rint l,rint r){
	int ans = inf,len = 0;
	rep(i,st[bl[l]],ed[bl[l]])
		if(l <= b[i].i && b[i].i <= r) tmp[++len] = b[i].x;
	rep(i,2,len) chkmin(ans,tmp[i] - tmp[i - 1]);
	return ans;
}

int qry1(rint l1,rint r1,rint l2,rint r2){
	int len = 0,p = r1 - l1 + 1,ans = inf;
	rep(i,st[bl[l1]],ed[bl[l1]])
		if(l1 <= b[i].i && b[i].i <= r1) tmp[++len] = b[i].x;
	rep(i,st[bl[l2]],ed[bl[l2]])
		if(l2 <= b[i].i && b[i].i <= r2) tmp[++len] = b[i].x;
	std::inplace_merge(tmp + 1,tmp + p + 1,tmp + len + 1);
	rep(i,2,len) chkmin(ans,tmp[i] - tmp[i - 1]); 
	return ans;
}

void init(){
	B = sqrt(n); C  = (n - 1) / B + 1;
	rep(i,1,C){
		st[i] = (i - 1) * B + 1;
		ed[i] = i == C ? n : i * B;
		rep(j,st[i],ed[i]) bl[j] = i,b[j].i = j,b[j].x = a[j];
		std::sort(b + st[i],b + ed[i] + 1);
	}
	rep(i,1,C){
		rep(j,1,C) if(i ^ j){
			rint p = st[j];
			rep(k,st[i],ed[i]){
				while(p < ed[j] && b[p + 1].x < b[k].x) ++p;
				D[b[k].i][j] = abs2(b[k].x - b[p].x);
				if(p < ed[j]) chkmin(D[b[k].i][j],abs2(b[k].x - b[p + 1].x));
			}
		}
		rep(j,st[i],ed[i]) rep(k,i + 2,C) chkmin(D[j][k],D[j][k - 1]);
		rep(j,st[i],ed[i]) Rep(k,i - 2,0) chkmin(D[j][k],D[j][k + 1]);
		rep(k,1,i - 1) rep(j,st[i] + 1,ed[i]) chkmin(D[j][k],D[j - 1][k]);
		rep(k,i + 1,C) Rep(j,ed[i] - 1,st[i]) chkmin(D[j][k],D[j + 1][k]);
		S[i][i] = qry0(st[i],ed[i]);
	}
	rep(i,1,C) rep(j,i + 1,C) S[i][j] = min2(min2(S[i][j - 1],S[j][j]),D[ed[j]][i]);
}	

int qry(rint l,rint r){
	if(bl[l] == bl[r]) return qry0(l,r);
	int ans = qry1(l,ed[bl[l]],st[bl[r]],r);
	if(bl[l] + 1 <= bl[r] - 1){	
		chkmin(ans,S[bl[l] + 1][bl[r] - 1]);
		chkmin(ans,min2(D[r][bl[l] + 1],D[l][bl[r] - 1]));
	}
	return ans;
}

int main(){
	read(n); rep(i,1,n) read(a[i]);
	init(); read(m);
	while(m--){
		int l,r; read(l,r);
		print(qry(l,r),'\n');
	}
	return flush(),0;
}