[NOI Online #1 提高組]氣泡排序
阿新 • • 發佈:2020-10-04
description:
題面已經說得很清楚了:
給定一個\(1 ∼ n\)的排列\(p_i\),接下來有\(m\)次操作,操作共兩種:
交換操作:給定\(x\),將當前排列中的第\(x\)個數與第\(x+1\)個數交換位置。
詢問操作:給定\(k\),請你求出當前排列經過\(k\)輪氣泡排序後的逆序對個數。 對一個長度為\(n\)的排列\(p_i\)
data range:
\(N<=2*10^5\)
solution:
感覺這是一道翻譯題
我們記\(c_i\)表示第i個位置和前面位置形成的逆序對數
那麼顯然總逆序對數為\(\sum{c_i}\)
考慮一次氣泡排序後對\(c_i\)有什麼影響
舉個例子:
原數列:\(4\) \(3\) \(1\) \(5\) \(2\)
\(c_i\):\(0\) \(1\) \(2\) \(0\) \(3\)
一次冒泡後:\(3\) \(1\) \(4\) \(2\) \(5\)
\(c_i`\):\(0\) \(1\) \(0\) \(2\) \(0\)
似乎發現什麼規律?
\(c_i`=max(0,c_{i+1}-1)\)
為什麼會這樣呢?
考慮相鄰的兩個數\(a_i\)和\(a_{i+1}\)(\(a_i>a_{i+1}\))
交換後會發現\(c_i`=max(0,c_{i+1}-1)\)(因為逆序對數量減少了一個)
那麼考慮如何統計答案
\(ans=\sum_{c_i>=k+1}{(c_i-k)}=\sum_{c_i>=k+1}c_i-k*\sum_{c_i>=k+1}1\)
於是用權值線段樹或權值樹狀陣列維護區間數的個數以及數的和就可以了
至於交換相鄰兩個數的操作,直接一波分類討論
code:
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=2e5+5; int n,m,num[N],p[N]; struct SGT { int c[N<<2];LL s[N<<2]; #define lc rt<<1 #define rc rt<<1|1 inline void up(int rt){c[rt]=c[lc]+c[rc],s[rt]=s[lc]+s[rc];} inline void upd(int rt,int l,int r,int pos,int v) { if(l==r){c[rt]+=v,s[rt]+=1ll*v*l;return;} int mid=l+r>>1; if(pos<=mid)upd(lc,l,mid,pos,v); else upd(rc,mid+1,r,pos,v); up(rt); } inline void query(int rt,int l,int r,int ll,int rr,int &cnt,LL &sum) { if(ll<=l&&r<=rr){sum+=s[rt],cnt+=c[rt];return;} int mid=l+r>>1; if(ll<=mid)query(lc,l,mid,ll,rr,cnt,sum); if(mid<rr)query(rc,mid+1,r,ll,rr,cnt,sum); } #undef lc #undef rc }A,B; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { scanf("%d",p+i); int ct=0;LL sm=0;A.query(1,0,n,p[i],n,ct,sm); B.upd(1,0,n,ct,1),num[i]=ct;//這裡注意num[i]可能為0,因此值域要從0開始 A.upd(1,0,n,p[i],1); } while(m--) { int t,c;scanf("%d%d",&t,&c); if(t==2) { if(c>=n){puts("0");continue;} int ct=0;LL sm=0;B.query(1,0,n,c+1,n,ct,sm); printf("%lld\n",sm-1ll*c*ct); } else { B.upd(1,0,n,num[c],-1),B.upd(1,0,n,num[c+1],-1); p[c]>p[c+1]?--num[c+1]:++num[c]; swap(p[c],p[c+1]),swap(num[c],num[c+1]); B.upd(1,0,n,num[c],1),B.upd(1,0,n,num[c+1],1); } } return 0; }