• 「NOI2020」时代的眼泪


    「NOI2020」时代的眼泪

    前言

    这种东西看到就给人一种

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

    啊啊啊啊啊啊啊啊啊啊啊

    [ ]

    问题分析

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

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

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

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

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

    [ ]

    Solution

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

    具体的:

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

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

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

    [ ]

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

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

    [ ]

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

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

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

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

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

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

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

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

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

    [ ]

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

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

    但是也可以通过离线去掉这个步骤,避免了一个(O(nsqrt 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
    ",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
    ",int(clock()-ti)),ti=clock();
    	SolvePoints();
    	fprintf(stderr,"Part1 %d
    ",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
    ",int(clock()-ti)),ti=clock();
    	SolveB2();
    	fprintf(stderr,"Part3 %d
    ",int(clock()-ti)),ti=clock();
    	rep(i,1,m) printf("%lld
    ",Ans[i]);
    }
    
  • 相关阅读:
    读书笔记
    STL 笔记
    Centos8如何配置网桥
    命令集合
    shared_ptr给管理的对象定制析沟函数
    share_ptr指向的对象的析构动作在创建的时候被捕获
    优秀博客
    字符串单词翻转
    带权图的最短路径算法(Dijkstra,Floyd,Bellman_ford)
    生产者与消费者 c++实现
  • 原文地址:https://www.cnblogs.com/chasedeath/p/14474237.html
Copyright © 2020-2023  润新知