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
",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;
}