「NOI2020」时代的眼泪

「NOI2020」时代的眼泪

前言

这种东西看到就给人一种

分块分块分块分块分块分块!

啊啊啊啊啊啊啊啊啊啊啊

\[\ \]

问题分析

这是一个二维区间顺序对问题,对于普通的区间顺序对问题,我们有简单分块解法

预处理整块的答案,有\(n\sqrt n\)个数要插入预处理,也就是有\(O(\sqrt n)\)个区间查询

对于散点暴力求,也是\(n\sqrt n\)个区间查询问题

那么离线+分块就可以做到\(O(\sqrt n)\)插入一个数,\(O(\sqrt 1)\)查询,并且有办法将空间实现到\(O(n)\)

那么对于二维区间考虑部分沿用上面的思路

\[\ \]

Solution

首先对于散块的部分,是完全一样的处理,可以\(O(n)\)内存实现

具体的:

散点之间可以暴力\(for\)答案,每次还需要一个二维区间个数查询

每次需要查询的散点又是一段区间

可以描述为\(O(m)\)个查询,总共查询\(O(m\sqrt n)\)个散点

\[\ \]

问题在于整块部分的查询\([p1,p2],[u,d]\)

对于同一个块内的答案,可以暴力预处理出来

\[\ \]

而块之间,可以转化为\([1,d]-[1,u-1]-[1,u-1]\times [u,d]\)

前面两个前缀型问题,可以用如下方法实现:

按照\(p_i\)从小到大插入,同时维护每个块内已经出现的个数

每次插入\(i\)后,对于\(i\)前面的块,会产生\(O(\sqrt n)\)对 顺序对

我们要查询的是一个块编号\([p1,p2]\)内块的关系,这是一个二维前缀和

可以把两个维度的前缀和分开给插入和查询

具体的,在插入时,处理\(S_{l,r}=\sum_{i\ge l} C_{i,r}\)

查询\([p1,p2]\)时,就可以暴力求\(S_{l,i}i\in[l,r]\)的和

这样可以分摊复杂度为\(O(n\sqrt n)\),并且内存为\(O(n)\),常数较小

\[\ \]

对于\([1,u-1]\times [u,d]\),从左到右一段段 查询过来,每次查询块内\([1,u-1]\)\(,[u,d]\)个数即可

这个统计和上面的块内答案统计都需要预处理每个数在块内排名

但是也可以通过离线去掉这个步骤,避免了一个\(O(n\sqrt n)\)的数组

\[\ \]

实际实现时,发现散块暴力的部分枚举起来实在太慢,所以块开大了一点,加了一点底层玄学优化

Loj Submission

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
typedef vector <int> V;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
	T s=0; int f=0;
	while(!isdigit(IO=getchar())) f|=IO=='-';
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return f?-s:s;
}

const int N=1e5+10,M=2e5+10,S=1000;

int n,m,A[N],len,P[N];
struct Blocker{
	int s[M],t[N];
	void clear(){ memset(s,0,(n+1)<<2),memset(t,0,(n+1)<<2); }
	void Add(int x){
		int p=x/len;
		rep(i,x,(p+1)*len-1) s[i]++;
		rep(i,p+1,n/len) t[i]++;
	}
	int operator [](const int &x) const{ return s[x]+t[x/len]; }
} B;
int L[M],R[M],U[M],D[M],p1[M],p2[M],I[M],T[M];
ll Ans[M];
struct Que{ int l,r,k,id; };
vector <Que> Q[N];
// 处理散点
void SolvePoints(){
	rep(i,1,n) {
		B.Add(A[i]);
		for(Que x:Q[i]) {
			rep(j,x.l,x.r) {
				int u=U[x.id],d=D[x.id];
				if(A[j]<u || A[j]>d) continue;
				if(j>i) cmin(d,A[j]-1);
				else cmax(u,A[j]+1);
				Ans[x.id]+=x.k*(B[d]-B[u-1]);
			}
		}
	}
}

vector <Pii> E[N];
// 处理块区间的 前缀逆序对
void SolveB1(){
	static ll s[S][S],c[S];
	rep(k,1,n) {
		int i=P[k],t=0,p=i/len;
		c[p]++;
		drep(j,p-1,0) t+=c[j],s[j][p]+=t;
		for(Pii x:E[k]) {
			int u=x.first,l=p1[u]+1,r=p2[u]-1;
			rep(j,l+1,r) Ans[u]+=s[l][j]*x.second;
		}
    }
}

//处理块内答案
void SolveB2(){
	static int s[S][S],C[N];
	rep(i,0,n/len) {
		int l=max(1,i*len),r=min(n,(i+1)*len-1);
		rep(j,1,n) C[j]=C[j-1]+(l<=P[j] && P[j]<=r);
		int L=C[n];
		rep(a,1,L+1) rep(b,a-1,L+1) s[a][b]=0;
		rep(a,l,r) rep(b,a+1,r) if(A[a]<=A[b]) s[C[A[a]]][C[A[b]]]++;
		drep(a,L,1) rep(b,a,L) s[a][b]+=s[a+1][b]+s[a][b-1]-s[a+1][b-1];
		rep(j,1,m) if(p1[j]<i && i<p2[j]) {
			Ans[j]+=s[C[U[j]-1]+1][C[D[j]]];
			Ans[j]-=1ll*T[j]*(C[D[j]]-C[U[j]-1]);
			T[j]+=C[U[j]-1];
		}
	}
}

// 本来是暴力for l,r内的逆序对的,但是太慢,加了一点底层优化
int Que(int i,int l,int r,int u,int d){
	if(r-l>45) {
		int mid=(l+r*3)/4;
		Q[l-1].pb({mid+1,r,-1,i});
		Q[mid].pb({mid+1,r,1,i});
		return Que(i,l,mid,u,d)+Que(i,mid+1,r,u,d);
	}
	int ans=0;
	rep(i,l,r) if(u<=A[i] && A[i]<=d) rep(j,i+1,r) ans+=A[i]<=A[j] && A[j]<=d;
	return ans;
}

int main(){
	freopen("tears.in","r",stdin),freopen("tears.out","w",stdout);
	n=rd(),m=rd(),len=ceil(sqrt(n/4.0));
	fprintf(stderr,"Block len=%d ,Block Count=%d\n",len,n/len);
	rep(i,1,n) P[A[i]=rd()]=i;
	clock_t ti=clock();
	rep(i,1,m) {
		I[i]=i,L[i]=rd(),R[i]=rd(),U[i]=rd(),D[i]=rd();
		p1[i]=L[i]/len,p2[i]=R[i]/len;
		if(p1[i]==p2[i]){ Ans[i]=Que(i,L[i],R[i],U[i],D[i]); continue; }
		Ans[i]=Que(i,L[i],(p1[i]+1)*len-1,U[i],D[i])+Que(i,p2[i]*len,R[i],U[i],D[i]);
		Q[L[i]-1].pb({p2[i]*len,R[i],-1,i});
		Q[p2[i]*len-1].pb({p2[i]*len,R[i],1,i});
		if(p1[i]<p2[i]-1) {
			Q[(p1[i]+1)*len-1].pb({L[i],(p1[i]+1)*len-1,-1,i});
			Q[p2[i]*len-1].pb({L[i],(p1[i]+1)*len-1,1,i});
			E[D[i]].pb(mp(i,1));
			E[U[i]-1].pb(mp(i,-1));
		}
	}
	fprintf(stderr,"Part0 %d\n",int(clock()-ti)),ti=clock();
	SolvePoints();
	fprintf(stderr,"Part1 %d\n",int(clock()-ti)),ti=clock();
	sort(I+1,I+m+1,[&](int x,int y){ return L[x]<L[y]; });
	SolveB1();
	fprintf(stderr,"Part2 %d\n",int(clock()-ti)),ti=clock();
	SolveB2();
	fprintf(stderr,"Part3 %d\n",int(clock()-ti)),ti=clock();
	rep(i,1,m) printf("%lld\n",Ans[i]);
}
posted @ 2021-03-03 14:21  chasedeath  阅读(614)  评论(0编辑  收藏  举报