1. 程式人生 > 實用技巧 >【洛谷6186】[NOI Online #1 提高組] 氣泡排序(樹狀陣列)

【洛谷6186】[NOI Online #1 提高組] 氣泡排序(樹狀陣列)

點此看題面

  • 給定一個長度為\(n\)的排列,有兩種操作:
    • 交換一對相鄰的數。
    • 詢問\(k\)輪氣泡排序之後的逆序對個數。
  • \(n,q\le2\times 10^5\)

簡單的結論題

記錄\(c_i\)表示第\(i\)個數左側有多少比它大的數。

則一輪氣泡排序相當於是從左到右把每個\(c_i=0\)的數移到下一個\(c_i=0\)的數左邊。

除此之外,所有數左側都會有某個比它大的數移到了它的右側,因此\(c_i\)\(1\)

那麼對於一次詢問相當於就是求出\(\sum_{i=1}^{n}\max\{c_i-k,0\}\)

這等同於所有大於等於\(k\)\(c_i\)之和減去大於等於\(k\)

\(c_i\)個數乘\(k\)

發現一次修改實際上只會影響到交換的這兩個位置的\(c_i\),因此就是單點修改。

那麼只要維護一個權值樹狀陣列即可。

程式碼:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LL long long
using namespace std;
int n,a[N+5],c[N+5];
struct TreeArray
{
	LL a[N+5];I void U(RI x,CI v) {++x;W(x) a[x]+=v,x-=x&-x;}//單點修改
	I LL Q(RI x,LL t=0) {++x;W(x<=n+1) t+=a[x],x+=x&-x;return t;}//字尾求和
}A,T1,T2;
int main()
{
	#define T(x,v) (T1.U(x,v),T2.U(x,v*x))
	RI Qt,i;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i) scanf("%d",a+i),c[i]=A.Q(a[i]),A.U(a[i],1),T(c[i],1);//求出初始c[i]
	RI op,x;W(Qt--) scanf("%d%d",&op,&x),op==2?printf("%lld\n",T2.Q(x)-T1.Q(x)*x):(T(c[x],-1),T(c[x+1],-1),//操作2直接查詢;操作1先清除原貢獻
		a[x]>a[x+1]&&--c[x+1],swap(a[x],a[x+1]),swap(c[x],c[x+1]),a[x]>a[x+1]&&++c[x+1],T(c[x],1),T(c[x+1],1),0);//交換,然後把貢獻加回去
	return 0;
}