what?什么是逆序对? 题目
逆序对定义为满足i
给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
#include
#include
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=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=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