1. 程式人生 > 實用技巧 >動態逆序對-CQOI2011

動態逆序對-CQOI2011

更好的閱讀體驗

[button color="info" icon="" url="https://www.luogu.com.cn/problem/P3157" type=""]題目傳送門[/button]


Problem

有一個長度為n的1~n的排列,現在進行m次刪除操作,每次給定刪除的元素,要求在每次刪除之前輸出序列裡的逆序對個數。


Solution

考慮最普通的靜態逆序對,顯然可以用樹狀陣列來維護,那麼每次刪除一個數p,逆序對個數減少的就是(p前面大於p的數)和(p後面小於p的數),立馬就有一個樸素的想法:在樹狀數組裡修改刪除,但是馬上也就能把這個想法切掉,因為刪除操作開始後,所有數字已經加入進去了,此時的統計是沒有意義的。

那麼我們就要考慮如何完成這個維護這個順序的問題,另一個想法就是用主席樹套樹狀陣列,但是我不會,所以讓我們來想想其他的辦法。

然後就考慮分塊。前面的塊裡的元素無論如何,一定在後面塊裡元素的前面。所以我們就考慮在刪除的元素的塊內暴力,其他的塊內直接利用前後關係來計算對答案的貢獻。

經過實踐,發現分100塊左右是最優的,就算卡滿時間複雜度也只有O(1000*m),大概5e7的時間複雜度,顯然是能過去的。

具體操作就是對每一塊需要查詢比某個元素x大或小的個數,那麼每一塊開兩個樹狀陣列就可以實現了,第一個在1處插1,a[i]處插-1,這樣我們ask(x)得到的就是大於它的元素個數,第二個在a[i]處插1,ask(x)得到是小於它的元素個數。

至於上面說的暴力是怎麼暴力呢?真就列舉原數列判斷p前面比它大的,p後面比它小的,這個操作是O(塊長)的,上面那個整塊的操作是O(塊數logn)的,logn大致是17,整體是差不多在O(1000)這樣一個級別,所以時間複雜度是正確的。

程式碼實現也挺簡單的。。至少比機房那個打主席樹套樹狀陣列還沒調出來的簡單。

#include<bits/stdc++.h>
#define reg register
#define int long long
#define len 1000
#define L(x) ((x)*len-len+1)
#define R(x) ((x)*len)
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e5+10,SN=110;
int a[N],num[N],c[SN][N][2];
int to[N],wz[N],vis[N],n,m;
inline int read(){
   int x=0,f=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
   while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
   return x*f;
}
inline void add(int u,int v,int pos,int p){
	for(reg int i=u;i<=n;i+=lowbit(i))
		c[pos][i][p]+=v;
}
inline int ask(int u,int pos,int p){
	int cnt(0);
	for(reg int i=u;i;i-=lowbit(i))
		cnt+=c[pos][i][p];
	return cnt;
}
inline void radd(int u,int v,int pos){
	add(u,v,pos,0),add(1,v,pos,1),add(u,-v,pos,1);
}
signed main(){
	cerr<<"len:"<<len<<endl;
	n=read(),m=read();
	int ans=0;
	for(reg int i=1;i<=n;i++){
		a[i]=read();
		ans+=ask(a[i],0,1);radd(a[i],1,0);
	}
	for(reg int i=1;i<=n;i++)
		num[i]=(i-1)/len+1,to[a[i]]=num[i],wz[a[i]]=i,radd(a[i],1,num[i]);
	while(m--){
		reg int p(read()),b=to[p],w=wz[p];
		printf("%lld\n",ans);
		radd(p,-1,b);vis[p]=1;
		for(reg int i=L(b),e=R(b);i<=e&&i<=n;i++)
			if(((i<w&&a[i]>a[w])||(i>w&&a[i]<a[w]))&&!vis[a[i]])ans--;
		for(reg int i=num[1],e=num[n];i<=e;i++){
			if(i<b)ans-=ask(p,i,1);
			if(i>b)ans-=ask(p,i,0);
		}
	}
	return 0;
}