「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)\)的數組
實際實現時,發現散塊暴力的部分枚舉起來實在太慢,所以塊開大了一點,加了一點底層玄學優化
#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]);
}