【题解】NOIO2020 冒泡排序(树状数组)

【题解】NOIO2020 冒泡排序(树状数组)

考虑在k次冒泡后的排列中的一个逆序对\(i<j,a_i>a_j\)

因为这个\(a_i\)的存在,意味着\(a_j\)没有被往右边交换过(否则不会存在一个\(a_i>a_j\))。

对于每个没有被交换到右边的数,他前面总共有\(k\)个比他大的数被交换走了。记\(ans[i]\)表示原序列\(i\)位置和之前的数构成的逆序对个数,那么\(k\)次交换后,对答案的贡献是\(ans[i]-k\)

那么答案就是
\[ \sum \max(0,\text{ans}[i]-k) \]
\(ans[i]\)的值域开两个树状数组,每次取所有大于\(k\)的所有\(ans\)之和,再减去\(ans[i]>k\)的个数乘以\(k\)就是答案。两个操作都是取一段后缀,直接树状数组即可。

至于交换操作,由于只影响了两个\(ans\),所以直接模拟一遍就行。

复杂度\(O(n\log n)\)。注意\(k\)要对\(n-1\)取min。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;  typedef long long ll;  
inline int qr(){
    int ret=0,f=0,c=getchar();
    while(!isdigit(c)) f|=c==45,c=getchar();
    while( isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=2e5+5;
int data[maxn],n,m;
ll seg1[maxn],seg2[maxn];
void add(ll*seg,int pos,ll val){
    ++pos;
    for(int t=pos;t<=n+1;t+=t&-t)
        seg[t]+=val;
}
ll que(ll*seg,int pos){
    ++pos;
    ll ret=0;
    for(int t=pos;t>0;t-=t&-t)
        ret+=seg[t];
    return ret;
}
ll que(ll*seg,int l,int r){return que(seg,r)-que(seg,l-1);}
int ans[maxn];
void del(int pos){add(seg1,ans[pos],-ans[pos]); add(seg2,ans[pos],-1);}
void add(int pos){add(seg1,ans[pos],ans[pos]); add(seg2,ans[pos],1);}

int main(){
    n=qr(); m=qr();
    for(int t=1;t<=n;++t)
        data[t]=qr(),ans[t]=t-1-que(seg1,data[t]),add(seg1,data[t],1);
    memset(seg1,0,sizeof seg1);
    for(int t=1;t<=n;++t) add(t);
    for(int t=1;t<=m;++t){
        int op=qr(),x=min(qr(),n);
        if(op==1){
            del(x),del(x+1);
            ans[x+1]-=data[x]>data[x+1];
            swap(data[x],data[x+1]); swap(ans[x],ans[x+1]);
            ans[x+1]+=data[x]>data[x+1];
            add(x),add(x+1);
        }else{
            ll ret=que(seg1,x,n)-que(seg2,x,n)*x;
            printf("%lld\n",ret);
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/winlere/p/12434056.html