「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]);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM