1. 程式人生 > 其它 >Solution -「NOI 2020」時代的眼淚

Solution -「NOI 2020」時代的眼淚

Description

Link

給出一個二維平面以及一些點,保證點不在同行 / 同列。每次詢問求出一個子矩陣裡面的順序對。

Solution

卡常,卡你嗎。

膜拜 dX。

基本是把 dX 的題解賀了過來所以沒啥參考的價值。

不過有很多細節的處理不一樣,大概能算個 \(\frac{1}{50}\) 成新?

對序列分塊,把貢獻分成 整塊 - 散塊 / 整塊 - 整塊/ 散塊 - 整塊 / 散塊 - 散塊 以及 散塊內部 / 整塊內部 共六種貢獻。

\(\textit{ans}_{0}(l,r,x,y)\) 為詢問 \(l,r,x,y\) 的答案。

同時預處理出 \(\textit{lb}(i,j),\textit{ub}(i,j)\)

分別表示在塊 \(i\) 中數 \(j\)std::lower_bound / std::upper_bound 值,下文如果寫成單元函式的形式那麼就是省去了第一維。

以及預先做一個塊內排序,記為 \(\textit{ord}(i,j)\),表示塊 \(i\) 中排序後的第 \(j\) 個元素。

注意本文在每個 subtask 定義的東西在其他 subtask 依然適用

  • 散塊 - 散塊;

兩邊的都是 \(\sqrt{n}\) 級別,拉出來分別排序後歸併計算順序對即可。

  • 散塊內部

考慮如何對 \(\textit{ans}_{0}(l,r,x,y)\) 進行容斥。

主要矛盾集中在:會出現 \((a\in[1,x),b\in[x,y])\)

這樣的貢獻。令 \(\textit{cnt}_{0}(i,j)\) 表示 \([\textit{lp},i]\)\(\textit{rank}_{1}\) 小於 \(j\) 的數的數量,其中 \(\textit{lp}\) 是當前塊的左端點,下同,如果出現 \(\textit{rp}\) 同理,\(\textit{rank}_{1}\) 的定義見下文。

則容斥可以寫為 \(\textit{ans}_{0}(l,r,x,y)=\textit{ans}_{0}(l,r,1,y)-\textit{ans}_{0}(l,r,1,x-1)-\sum_{i=l}^{r}[a_{i}\in[x,y]]\cdot\textit{cnt}_{0}(i,\textit{lb}(x)-1)\)

又有 \(\textit{ans}_{0}(l,r,1,x)=\sum_{i=l}^{r}[a_{i}\leqslant x]\cdot\textit{cnt}_{0}(i,\textit{rank}_{1}(i))\),我們就可以做到單次 \(\mathcal{O}(\sqrt{n})\),注意的 \(l,r\) 觸及散塊邊界者不同時,對 \(\textit{cnt}_{0}\) 的容斥也有區別。

  • 整塊 - 整塊

\(\textit{cnt}_{1}(i,j)\) 為塊 \([1,i]\)\(\leqslant j\) 的元素個數,\(\textit{ans}_{1}(L,R,x,y)\) 為塊 \([L,R]\) 的答案,以及 \(\textit{rank}_{0}(i,j)\) 是塊 \(i\) 中排名 \(j\) 的元素在原陣列中的下標。

我們掏出傳統容斥:\(\textit{ans}_{1}(L,R,x,y)=\textit{ans}_{1}(L,R,1,y)-\textit{ans}_{1}(L,R,1,x-1)-\sum_{i=L}^{R}P_{i}Q_{i}\)\(P_{i}\) 是塊 \([L,i)\)\(<x\) 的元素個數,\(Q_{i}\) 是塊 \(i\)\(\in[x,y]\) 的元素個數。

考慮算 \(\textit{ans}_{1}(L,R,1,x)\)

定義 \(\textit{rank}_{1}(i,j)\) 為塊 \(i\) 中第 \(j\) 個元素的排名(從小到大,下同),\(\textit{rank}_{2}(i,j)\) 為塊 \(i\) 中滿足 \(<j\) 的最大元素的排名,\(\textit{pre}_{b}(i,j)\) 為塊 \([i,j]\) 中所有 \(<\textit{rank}_{1}(i,j)\) 的元素數量。

易知 \(\textit{pre}_{b}(i,j)=\textit{cnt}_{1}(i,\textit{rank}_{1}(i,j)-1)\),再定義 \(\overset{\sqrt{n},\sqrt{n},\sqrt{n}}{\textit{cp}_{0}(i,L,r)}\) 為塊 \([1,L]\) 與塊 \(i\)\(r\) 小的元素組成的順序對數量,同樣易知 \(\textit{cp}_{0}(i,L,r)=\sum_{k\in T}[\textit{rank}_{1}(i,k)\leqslant r]\cdot\textit{pre}_{b}(L,\textit{rank}_{1}(i,k))\),其中 \(T\) 是塊 \(i\) 的元素集。但這樣搞狀態數 \(\mathcal{O}(n\sqrt{n})\) 轉移還要 \(\mathcal{\sqrt{n}}\) 而且不好字首和。

不過可以發現使用 \(\textit{ord}\) 陣列 \(\textit{cp}_{0}\) 就可以遞推了:\(\textit{cp}_{0}(i,L,r)=\sum_{k=lp}^{r+lp-1}\textit{pre}_{b}(L,k)=\textit{cp}_{0}(i,L,r-1)+\textit{pre}_{b}(L,r+lp-1)\)

然後 \(\textit{ans}_{1}(L,R,1,x)=\sum_{i=L+1}^{R}\textit{cp}_{0}(i,i-1,\textit{rank}_{2}(i,x))-\textit{cp}_{0}(i,L-1,\textit{rank}_{2}(i,x))\)

預處理 \(\textit{cp}_{0}\)\(\mathcal{O}(n\sqrt{n})\),單次回答 \(\mathcal{O}(\sqrt{n})\)

  • 散塊 - 整塊

列舉散塊裡面的元素,利用 \(\textit{cnt}_{1}(i,j)\) 計算答案。

具體是令散塊元素集為 \(T\),整塊編號為 \(L,R\)\(\sum_{i\in T}\textit{cnt}_{1}(R,i)-\textit{cnt}_{1}(L-1,i)\)

  • 整塊 - 散塊

和上面有什麼區別嗎?

  • 整塊內部

預處理陣列 \(\overset{\sqrt{n},\sqrt{n},\sqrt{n}}{\textit{cp}_{1}(i,x,y)}\) 表示取 \(\textit{ord}(i,x\dots y)\) 組成的序列的順序對數量。

\(\textit{rank}_{0}\) 來預處理:\(\textit{cp}_{1}(i,x,y)=\textit{cp}_{1}(i,x,y-1)+\textit{cnt}_{0}(\textit{rank}_{0}(i,y),y-1)-\textit{cnt}_{0}(\textit{rank}_{0}(i,y),x-1)\)


綜上,這個問題得以一個 \(\mathcal{O}(n\sqrt{n})\) 的線上演算法解決。

程式碼也是抄的 dX,像個 shit 一樣就摺疊了。

% 死X
//almost copied from dead_X sry
//kouhu has no qiantu
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
inline int Read()
{
	int x=0;char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9') x=x*10+(c&15),c=getchar();
	return x;
}
const int N=101111,A=460,BS=A+10;
ll cp0[BS][BS][BS];
int a[N],rk0[BS][BS],cnt0[BS][N],cp1[BS][BS][BS],lb[BS][N],rk1[N],cnt1[BS][N],L[BS],R[BS];
bool cmp(int x,int y) { return a[x]<a[y]; }
namespace IO{
    const int sz=1<<22;
    char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
    inline char gc(){
        return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
    }
    template<class T> void gi(T& x){
        x=0; char c=gc();
        for(;c<'0'||c>'9';c=gc());
        for(;c>='0'&&c<='9';c=gc())
            x=(x<<3)+(x<<1)+(c-'0');
    }
    inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
    inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
    template<class T> void pi(T x,char c='\n'){
        if(x<0) x=-x;
        if(x==0) pc('0'); int t=0;
        for(;x;x/=10) p[++t]=x%10+'0';
        for(;t;--t) pc(p[t]); pc(c);
    }
    struct F{~F(){flush();}}f; 
}
using IO::gi;
using IO::pi;
inline int read() { int r; gi(r); return r; }
int main(){
#ifdef ONLINE_JUDGE
	freopen("tears.in","r",stdin);
	freopen("tears.out","w",stdout);
#endif
	int n=read(),m=read(),B=n/A;
	for(int i=0;i<n;++i)a[i]=read();
	for(int i=n;i<(B+1)*A;++i)a[i]=i;
	for(int i=0;i<=B;++i){
		for(int j=i*A,k=0;k<A;++j,++k)rk0[i][k]=j;
		sort(rk0[i],rk0[i]+A,[](int x,int y){return a[x]<a[y];});
		for(int j=0;j<A;++j)rk1[rk0[i][j]]=j,cnt0[j][rk0[i][j]]=1;
		for(int j=i*A+1;j<(i+1)*A;++j)
			for(int k=0;k<A;++k)cnt0[k][j]+=cnt0[k][j-1];
		for(int j=i*A;j<(i+1)*A;++j)
			for(int k=1;k<A;++k)cnt0[k][j]+=cnt0[k-1][j];
		for(int j=i*A;j<(i+1)*A;++j)++cnt1[i][a[j]];
		if(i)for(int j=1;j<=101000;++j)cnt1[i][j]+=cnt1[i-1][j];
		for(int j=1,k=0;j<=101000;++j)(k<A)&&(j>=a[rk0[i][k]])&&(++k),lb[i][j]=k;
	}
	for(int i=0;i<=B;++i)
		for(int j=1;j<=101000;++j)cnt1[i][j]+=cnt1[i][j-1];
	for(int i=1;i<B;++i)for(int j=0;j<i;++j)for(int k=0;k<A;++k)
		cp0[i][j][k+1]=cnt1[j][a[rk0[i][k]]]+cp0[i][j][k];
	for(int i=0;i<B;++i)for(int j=0;j<A;++j)for(int k=j+1;k<A;++k)
		cp1[i][j][k]=cp1[i][j][k-1]+cnt0[k-1][rk0[i][k]]-((j==0)?0:cnt0[j-1][rk0[i][k]]);
	for(;m;--m){
		int l=read()-1,r=read()-1,x=read(),y=read(),bl=l/A,br=r/A;
		if(bl==br){
			int ans=0;
			for(int i=l;i<=r;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[rk1[i]-1][i]-((l%A)?cnt0[rk1[i]-1][l-1]:0);
				if(lb[bl][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[lb[bl][x-1]-1][i]-((l%A&&lb[bl][x-1])?cnt0[lb[bl][x-1]-1][l-1]:0);
			}
			pi(ans);
		}
		else{
			ll ans=0;
			for(int i=l;i<(bl+1)*A;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[rk1[i]-1][i]-((l%A)?cnt0[rk1[i]-1][l-1]:0);
				if(lb[bl][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[lb[bl][x-1]-1][i]-((l%A&&lb[bl][x-1])?cnt0[lb[bl][x-1]-1][l-1]:0);
				if(x<=a[i]&&a[i]<=y)ans+=cnt1[br-1][y]-cnt1[bl][y]-cnt1[br-1][a[i]]+cnt1[bl][a[i]];
			}
			for(int i=br*A;i<=r;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[rk1[i]-1][i];
				if(lb[br][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[lb[br][x-1]-1][i];
				if(x<=a[i]&&a[i]<=y)ans+=cnt1[br-1][a[i]]-cnt1[bl][a[i]]-cnt1[br-1][x-1]+cnt1[bl][x-1];
			}
			int lt=0,rt=0;
			for(int i=0;i<A;++i){
				if(rk0[bl][i]>=l&&x<=a[rk0[bl][i]]&&a[rk0[bl][i]]<=y)L[++lt]=rk0[bl][i];
				if(rk0[br][i]<=r&&x<=a[rk0[br][i]]&&a[rk0[br][i]]<=y)R[++rt]=rk0[br][i];
			}
			for(int i=1,t=1;i<=rt;++i){
				while(t<=lt&&a[L[t]]<a[R[i]])++t;
				ans+=t-1;
			}
			for(int i=bl+1;i<br;++i)if(lb[i][y])ans+=cp1[i][lb[i][x-1]][lb[i][y]-1];
			for(int i=bl+2;i<br;++i)
				ans+=cp0[i][i-1][lb[i][y]]-cp0[i][bl][lb[i][y]]-cp0[i][i-1][lb[i][x-1]]+cp0[i][bl][lb[i][x-1]],
				ans-=ll(cnt1[i][y]-cnt1[i-1][y]-cnt1[i][x-1]+cnt1[i-1][x-1])*(cnt1[i-1][x-1]-cnt1[bl][x-1]);
			pi(ans);
		}
	}
	return 0;
}