求逆序对
描述
给定一个序列a1,a2,…,an,如果存在iaj,那么我们称之为逆序对,求逆序对的数目
输入
第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数。
N<=10^5。Ai<=10^5
输出
两行,第一行为所有逆序对总数,第二行为本质不同的逆序对总数。
输入
4 3 2 3 2
输出3
1
思路:
此题要求我们求逆序对的总个数,不难,关键是第二问,如何求本质不同的逆序对?
我们在解决此问题之前不妨来看看第一问是怎么求的。众所周知当a[i]>a[j]&&i<j时a[i]与a[j]构成一个逆序对,于是我们便可以在这n个数字的值域上开一个树状数组,
用来维护每一个数字在它之前出现了多少小于等于它的数字。我们首先枚举每一个位置,将这个数加入树状数组(赋为1),然后求出在这个位置之前有多少个1,即是我们所求,
求出这个之后,因为这个数字如果要与其他数字构成逆序对,必须大于其他的数字,因此我们拿这个数字的位置减去前面小于等于它的数字的个数,便是逆序对的个数。
我们来模拟一个样例3 2 3 2
首先把3加入树状数组0 0 1 ask(3)=1,ans+=i-1=1-1=0;
把2加入 0 1 1 ask(2)=1,ans+=i-1=2-1=1;
再把3加入,因为3已经有了0 1 2 ask(3)=3,ans+=i-3=3-3=0;
最后加2和3一样 0 2 2 ask(2)=2,ans+=i-2=4-2=2,所以ans=3;
再来仔细看看上面的过程,我们再来求的本质不同的逆序对是不是有一些思路了?
我们要求的不就相当于只往树状数组里面加一个和之前不同数字的逆序对吗,也就是说,当我们遇到和之前某个位置一样的数字的时候,我们只需要记住它上次出现时所产生的逆
序对个数,然后在以后我们遇到相同数字的时候,不再往里面加数字,而是直接更新这个位置出现的这个数所产生的逆序对就可以了。可以自己手动画一下样例帮助理解。
具体解释看代码。
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define mod 1000000009 4 #define lowbit(x) x&(-x) 5 using namespace std; 6 ll n,sum[100005],b[100005],ans,num,f[100005],in[100005]; 7 struct data{ 8 ll v,id; 9 }a[100005]; 10 void add(ll x,ll val) 11 { 12 while(x<=100001) 13 { 14 sum[x]+=val; 15 x+=lowbit(x); 16 } 17 } 18 ll ask(ll x) 19 { 20 ll ans=0; 21 while(x) 22 { 23 ans+=sum[x]; 24 x-=lowbit(x); 25 } 26 return ans; 27 } 28 int main() 29 { 30 scanf("%lld",&n); 31 for(ll i=1;i<=n;i++) 32 scanf("%lld",&a[i].v); 33 for(ll i=1;i<=n;i++) 34 { 35 add(a[i].v,1);//加入树状数组 36 ans+=i-ask(a[i].v);//用当前位置减去比它小的数字个数 37 }//统计有多少个逆序对 38 memset(sum,0,sizeof(sum)); 39 for(ll i=n;i>=1;i--)//倒循环枚举 40 { 41 if(in[a[i].v]==0)//如果这个数曾经没有被加入到树状数组中过 42 { 43 add(a[i].v,1);//加入 44 in[a[i].v]=1;//打上标记 45 } 46 num-=f[a[i].v];//用总个数减去之前那个位置出现的数所产生的逆序对个数,因为之前那个位置能产生的逆序对现在这个位置也一定能,现在先减去,在后面再加上即可 47 f[a[i].v]=ask(a[i].v-1);//用现在这个位置出现的数所产生的逆序对个数更新之前那个位置出现的数所产生的逆序对个数(包含上一句话减掉的) 48 num+=f[a[i].v];//加上这个位置出现的那个数所产生的逆序对个数 49 } 50 printf("%lld %lld",ans,num); 51 return 0; 52 }
于是,这道题就被我们这么愉快地解决了^_^
思路如果有不正确的地方欢迎各大佬指正