建议做这题之前先做 洛谷 P2617 Dynamic Rankings
what?什么是逆序对? 题目
逆序对定义为满足i<j,且Ai>Aj的数对(i,j),则称(A[ i ],A[ j ])为数组A中的一个逆序对。
题目大意
给1到n的一个排列,按照某种顺序依次删除m个元素,输出每次删除一个元素之前统计整个序列的逆序对数。
10%的数据中,m,n≤100;
20%的数据中,m,n≤1000;
50%的数据中,m,n≤10000;
对于所有数据,m,n≤100000。
题目分析
找出未删除前所有的逆序对(用树状数组来完成比较快)。
当删除某个数时,删除与其有关逆序对即可。
这里是统计逆序对!
long long int ans=0;
memset(sum,0,sizeof(sum));
for(int i=n;i>=1;i--)
{
add(a[i],1);
r[i]=getsum(a[i]-1);
ans+=r[i];
}
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
{
add(n-a[i]+1,1);
l[i]=getsum(n-a[i]);
}
看懂了请自行跳过这一段!!!
没看懂的进来啊!!!
我们来举个例子吧!
such as:1 5 3 4 2。
表格是这样的!r表示在a[i]后与a[i]有关的逆序对的值,aa表示出现的次数,zz表示数的值。
r | 0 | 0 | 0 | 0 | 0 |
aa | 0 | 0 | 0 | 0 | 0 |
zz | 1 | 2 | 3 | 4 | 5 |
是从后面开始插入的哦!
for(int i=n;i>=1;i--)
插入2。
r | 0 | 0 | 0 | 0 | 0 |
aa | 0 | 1 | 0 | 0 | 0 |
zz | 1 | 2 | 3 | 4 | 5 |
插入4。
r | 0 | 0 | 0 | 1 | 0 |
aa | 0 | 1 | 0 | 1 | 0 |
zz | 1 | 2 | 3 | 4 | 5 |
插入3。
r | 0 | 0 | 1 | 1 | 0 |
aa | 0 | 1 | 1 | 1 | 0 |
zz | 1 | 2 | 3 | 4 | 5 |
插入5。
r | 0 | 0 | 1 | 1 | 3 |
aa | 0 | 1 | 1 | 1 | 1 |
zz | 1 | 2 | 3 | 4 | 5 |
插入1。
r | 0 | 0 | 1 | 1 | 3 |
aa | 1 | 1 | 1 | 1 | 1 |
zz | 1 | 2 | 3 | 4 | 5 |
ans=1+1+3=5;
是的就是解释的就是这一段!
long long int ans=0;//记录逆序对的总数
for(int i=n;i>=1;i--)//记录数列右边比我小的,从右边开始插入
{
add(a[i],1);
r[i]=getsum(a[i]-1);//记录在它后面且比它小的数出现的次数,就是逆序对的个数
ans+=r[i];
}
然后后面还有一段也怪怪的!l表示在a[i]前与a[i]有关的逆序对的值。
表格的顺序是反过来的。就像这样。
r | 0 | 0 | 0 | 0 | 0 |
aa | 0 | 0 | 0 | 0 | 0 |
zz | 5 | 4 | 3 | 2 | 1 |
还有一点不一样就是这次是从左边开始插入。
for(int i=1;i<=n;i++)
插入1。
l | 0 | 0 | 0 | 0 | 0 |
aa | 0 | 0 | 0 | 0 | 1 |
zz | 5 | 4 | 3 | 2 | 1 |
插入5。
l | 0 | 0 | 0 | 0 | 0 |
aa | 1 | 0 | 0 | 0 | 1 |
zz | 5 | 4 | 3 | 2 | 1 |
插入3。
l | 0 | 0 | 1 | 0 | 0 |
aa | 1 | 0 | 1 | 0 | 1 |
zz | 5 | 4 | 3 | 2 | 1 |
插入4。
l | 0 | 1 | 1 | 0 | 0 |
aa | 1 | 1 | 1 | 0 | 1 |
zz | 5 | 4 | 3 | 2 | 1 |
插入2。
l | 0 | 1 | 1 | 3 | 0 |
aa | 1 | 1 | 1 | 1 | 1 |
zz | 5 | 4 | 3 | 2 | 1 |
通过这些统计出与a[i]有关的逆序对。
但是你觉得出题人会有这么善良?!这道题就这样完了?!so easy?!
手动模拟发现删除的其实是有重复的,what?
在删除里的数找出其对应多删的逆序对,把减多的加回来就好啦!
ans=ans-l[w]-r[w];//减去有关这个数的逆序对
ans+=solve(w);//把删去的重复的加回来
问:哪里有重复的?!
比如说1 5 4 3 2,有6对逆序对(5,4),(5,3),(5,2),(4,3),(4,2),(3,2)。删去4,剩下逆序对为6-1-2=3。再删去3,剩下的逆序对为3-2-1=0。没了!?但是剩下的明明就是1 5 2,还有一对。怎么回事?因为(4,3)这组逆序对减了两次。要加回来的啊。
long long int solve(int x)
{
long long int sss=0;//sss用来记录加回来的逆序对
//分两部分处理
sss+=ll(x);//处理前面的多减的逆序对数
sss+=rr(x);//处理后面的多减的逆序对数
op=1; v=a[x];
for(int i=x;i<=n;i+=lowbit(i)) updata(root[i],1,n);//在序列上加上这个数
return sss;
}
每删去一个数在相应有关的树状数套主席树组进行处理,根表示维护的范围,维护区间中数的个数,用主席树来维护计算即可。这么说可能还有点懵,我根本不知道你在说什么,那就抽出处理前面多减逆序对的代码,结合思路讲讲吧。
long long int ll(int x)
{
//这时候所有的数都是在x左边的,找出比x大的即为删除的逆序对中重复的
long long int sss=0;
int tr1=0,aa[31];
for(int i=x-1;i>=1;i-=lowbit(i)) aa[++tr1]=root[i];//记住记住记住重要的事情说三遍是第x-1棵线段树,因为找比x大的不包括x本身嘛
//记录下与x-1的有关的主席树
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[x]<=mid)//往左边跳,说明右边的数都比我大
{
for(int i=1;i<=tr1;i++)
{
sss+=tot[rs[aa[i]]];//加起来
aa[i]=ls[aa[i]];//继续向左边找
}
r=mid;
}
else//往右边跳,说明左边的数都比我小,暂时还没有重复的逆序对
{
for(int i=1;i<=tr1;i++) aa[i]=rs[aa[i]];//继续向右边找
l=mid+1;
}
}
return sss;
}
代码
#include<cstdio>
#include<cstring>
int n,m,a[100010],h[100010],sum[100010],l[100010],r[100010];
int op,v,ls[10000010],root[100010],rs[10000010],t=0;
long long int tot[10000010];
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int s)
{
while(x<=n)
{
sum[x]+=s;
x+=lowbit(x);
}
}
//以上是树状数组
int getsum(int x)
{
int s=0;
while(x>0)
{
s+=sum[x];
x-=lowbit(x);
}
return s;
}
void updata(int &now,int l,int r)
{
if(now==0) now=++t;
tot[now]+=op;
if(l==r) return ;
int mid=(l+r)/2;
if(v<=mid) updata(ls[now],l,mid);
else updata(rs[now],mid+1,r);
}
long long int ll(int x)
{
//这时候所有的数都是在x前的,所以要找出比x大的即为删除的逆序对中重复的
long long int sss=0;
int tr1=0,aa[31];
for(int i=x-1;i>=1;i-=lowbit(i)) aa[++tr1]=root[i];
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[x]<=mid)//在左边,说明右子树的个数都比x大
{
for(int i=1;i<=tr1;i++)
{
sss+=tot[rs[aa[i]]];
aa[i]=ls[aa[i]];
}
r=mid;
}
else
{
for(int i=1;i<=tr1;i++) aa[i]=rs[aa[i]];
l=mid+1;
}
}
return sss;
}
long long int rr(int x)
{
//这时候所有的数都是在x后的,所以要找出比x小的即为删除的逆序对中重复的
long long int sss=0;
int tr1=0,tr2=0,aa[31],bb[31];
for(int i=x;i>=1;i-=lowbit(i)) aa[++tr1]=root[i];
for(int i=n;i>=1;i-=lowbit(i)) bb[++tr2]=root[i];
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[x]<=mid)//在左边,说明右子树的个数都比x大
{
for(int i=1;i<=tr1;i++) aa[i]=ls[aa[i]];
for(int i=1;i<=tr2;i++) bb[i]=ls[bb[i]];
r=mid;
}
else
{
for(int i=1;i<=tr2;i++)
{
sss+=tot[ls[bb[i]]];
bb[i]=rs[bb[i]];
}
for(int i=1;i<=tr1;i++)
{
sss-=tot[ls[aa[i]]];
aa[i]=rs[aa[i]];
}
l=mid+1;
}
}
return sss;
}
long long int solve(int x)
{
long long int sss=0;//sss用来记录加回来的逆序对
//分两部分处理
sss+=ll(x);//处理前面的多减的逆序对数
sss+=rr(x);//处理后面的多减的逆序对数
op=1; v=a[x];
for(int i=x;i<=n;i+=lowbit(i)) updata(root[i],1,n);//在序列上加上这个数
return sss;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
h[a[i]]=i;
}
long long int ans=0;//记录逆序对的总数
memset(sum,0,sizeof(sum));
for(int i=n;i>=1;i--)
{
add(a[i],1);
r[i]=getsum(a[i]-1);
ans+=r[i];
}
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
{
add(n-a[i]+1,1);
l[i]=getsum(n-a[i]);
}
while(m--)
{
int x,w; scanf("%d",&x);
printf("%lld\n",ans);//出删除前的逆序对个数
w=h[x];
ans=ans-l[w]-r[w];//减去有关这个数的逆序对
ans+=solve(w);//把删去的重复的加回来
}
return 0;
}
请记住最爱你的long long