Luogu P3157 [CQOI2011]动态逆序对
解法:树状数组维护主席树
求出原序列的逆序对个数(树状数组预处理)
再依输入把该元素在序列中的逆序对个数减去
但肯定减多了啊,
减多的就是该元素放入被删除元素所组成的序列中的逆序对
加回来(*树状数组+主席树 向左向右分别寻找)
[*我认为的难点,当时zxr讲的时候只听懂了大概思路,感谢lhy的后期帮助TvT]
最后把该元素放入被删除数所组成的序列中
空间开不对RE了好多次q-q
#include<cstdio>
#include<cstring>
int n,m,a[100100],len=0,tree[100100];
struct nod1{int lc,rc;long long c;}tr[20000100];
int root[100100],id[100100],r[100100],l[100100];
long long ans=0;
int lowbit(int x)
{
return x&-x;
}//玄学lowbit
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i))
tree[i]+=1;
}
//加入树状数组中
long long getsum(int x)
{
int tot=0;
for(int i=x;i>=1;i-=lowbit(i))
tot+=tree[i];
return tot;
}
//树状数组求和
void update(int &rt,int l,int r,int x)
{
if(rt==0)
{
len++;
rt=len;
}
tr[rt].c++;
if(l==r)return ;
int mid=(l+r)/2;
if(a[x]<=mid)update(tr[rt].lc,l,mid,x);
else update(tr[rt].rc,mid+1,r,x);
}//加入被删除序列(主席树)
long long left(int x)
{
//找左边比我大的数
int numx=0,familyx[1010];
for(int i=x-1;i>=1;i-=lowbit(i))
{
numx++;familyx[numx]=root[i];
}
long long total=0;
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[x]<=mid)
{
for(int i=1;i<=numx;i++)
{
//如果小于等于mid
total+=tr[tr[familyx[i]].rc].c;
//那么右边的数都比我大,加起来
familyx[i]=tr[familyx[i]].lc;
//向左边继续寻找
}
r=mid;
}
else
{
for(int i=1;i<=numx;i++)
//我大于mid,左边都比我小,暂时没有逆序对
familyx[i]=tr[familyx[i]].rc;
//向右边继续寻找
l=mid+1;
}
}
return total;
}
long long right(int x)
{
//找右边比我小的数
int y=n,numx=0,numy=0;
int familyx[1010],familyy[1010];
for(int i=x;i>=1;i-=lowbit(i))
{
numx++;familyx[numx]=root[i];
}
for(int i=y;i>=1;i-=lowbit(i))
{
numy++;familyy[numy]=root[i];
}
int l=1,r=n,total=0;
while(l<r)
{
int mid=(l+r)/2;
if(a[x]<=mid)
{
//我小于等于mid
//正常,暂时没有逆序对 、
//向左边继续寻找
for(int i=1;i<=numx;i++)
familyx[i]=tr[familyx[i]].lc;
for(int i=1;i<=numy;i++)
familyy[i]=tr[familyy[i]].lc;
r=mid;
}
else
{
//我大于mid,所以lc里都是比我小的
//左边都小于我 ,都是逆序对
for(int i=1;i<=numx;i++)
{
total-=tr[tr[familyx[i]].lc].c;
//减去1~x比起我小的个数
familyx[i]=tr[familyx[i]].rc;
}
for(int i=1;i<=numy;i++)
{
total+=tr[tr[familyy[i]].lc].c;
//再加上1~n里比我小的个数
//就是x~n里比我小的个数
familyy[i]=tr[familyy[i]].rc;
}
l=mid+1;
}
}
return total;
}
long long work(int x)
{
int tot=0;
tot+=left(x);
//在被删除序列左边查找
tot+=right(x);
//在被删除序列右边查找
for(int i=x;i<=n;i+=lowbit(i))
update(root[i],1,n,x);
//把这个元素加入被删除序列
return tot;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
id[a[i]]=i;//如果是这个值的元素对应原序列位置
}
for(int i=n;i>=1;i--)
{//把序列倒着放入树状数组中
r[i]=getsum(a[i]-1);
//getsum得到的计数是在当前元素之前放的,
//所以在序列中是在当前元素后面的
//又因为getsum的是a[i]-1,所以被计数的元素值比当前元素小
//所以就知道了有多少个序列中在我后面又比我小的
ans+=r[i];//把这些加起来就是原序列的逆序对
add(a[i]);
}
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++)
{//正着放入
//!!!但是树状数组中值大的在前
l[i]=getsum(n-a[i]);
//因为值反过来存,所以getsum(n-a[i])
//就是在我前面(已经放了才能被计数),比我大的
add(n-a[i]+1);
//值大的放前面,小的放后面
}
for(int i=1;i<=m;i++)
{
int x;
printf("%lld\n",ans);
scanf("%d",&x);
x=id[x];//用序列中的编号去查找更方便
ans-=(l[x]+r[x]);
//减去在原序列中这个元素的逆序对
ans+=work(x);
//加回减多的逆序对
//就是这个元素放进被删除数的序列中的逆序对
}
}
树状数组求逆序对真的十分神奇!