拖了很久今天终于下定决心来解决这道题了,这么经典的题目居然没有人写题解,本小白就来水一发(大佬勿喷),
希望能对大家有点帮助,主要讲下思想和两种方法的实现,感觉难点问题官方题解已经讲的很清楚了。
归并排序解法:
首先我们来看下为什么归并排序可以计算逆序对对数量:
比如一个数列为 5 4 1 2 3
首先在归并排序将数列分为两部分 5 4 1 和 2 3
左边 5 4 1再次分成两部分 5 4 和 1
然后5 4再被分为两部分,5和4
接着便开始逐步合并(重点来了):在合并的时候,由于5 排在 4前面,由于是从小到打排序,必然要先放置4,再放置5,这个时候我们便可以将逆序对+1了,该部分合并完成后变为 4 5,接着与 1合并,由于1比4,5都小,还排在4,5后面,这个时候要把1提前,变为1,4,5这样逆序对就+2,接下来的合并相同,这样排序完成便可以计算出逆序对的个数了。
注:这里只是思想,重点是代码!!!
#include<bits/stdc++.h> using namespace std; int a[100000],YHT[100086];//a数组为合并时候需要用于暂存的数组,YHT为初始数据存储的数组 int ans=0,l,r; inline int read(){ //快读模板 int ans = 0, f = 1; char ch = getchar(); if (ch == '-') f *= -1; while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { ans = ans * 10 + ch - '0'; ch = getchar(); } return ans * f; } void mergesort(int l,int r)//归并排序 { if(l==r) return;//先判断递归结束条件,当区间段只有一个元素的时候便可以结束 int mid=(l+r)>>1;// (l+r)/2与(l+r)>>1等同,但是后者更快,详情大家可以看看百度看看位运算; if(l<=mid) mergesort(l,mid); //左递归 if(mid+1<=r) mergesort(mid+1,r);//右递归 int ln=l,rn=mid+1,k=l; //合并操作 比如l-mid 为 1 3 4 mid+1-r为 2 6 7 一开始ln指向左部第一个元素即1,rn指向右部第一个元素即2,k用于存储的时候记录位置,一开始指向该区段最左边位置 while(ln<=mid&&rn<=r) { if(YHT[ln]<YHT[rn]) a[k++]=YHT[ln++]; else { a[k++]=YHT[rn++];//右部的放到前面来 ans+=mid-ln+1;//mid-ln+1为还没有放置的左部元素,即为和这部操作放置元素组成逆序对的元素 } } while(ln<=mid) a[k++]=YHT[ln++]; while(rn<=r) a[k++]=YHT[rn++]; for(int i=l;i<=r;i++)//将暂存数组中的数据放入主数组中 { YHT[i]=a[i]; } } int main() { int n,m; n=read(); for(int i=0;i<n;i++) { YHT[i]=read(); } mergesort(0,n-1); m=read(); ans%=2;//判断逆序对个数的奇偶性 while(m--) { l=read(); r=read(); int p=(r-l+1)*(r-l)/2; if(p&1) ans^=1;//p&1与p%2==1等同,因为只有p是奇数才可以能对逆序对的奇偶性产生影响,因为ans非0,即1,相互转化用异或即可 if(ans) printf("dislike\n"); else printf("like\n"); } }
2.树状数组
树状数组能快速求出前缀和,这里可以讲前缀和看作是逆序对的数列,还是举个例子吧
元素:5 4 1 3 2
下标:1 2 3 4 5
我们可以讲数组从大到小排序,下标不变:
排序后为:
元素 5 4 3 2 1
下表 1 2 4 5 3
这样就能保证先加入比后加入的大
初始状态树状数组为0 0 0 0 0
先加入5,5对应的下标为1,树状数组变为1 0 0 0 0
接着加入4,4对应的下标是2,树状数组变为 1 1 0 0 0
这时我们发现2前面已经有加入的,这样便构成一个逆序对,需要加1
接着加入3,3对应的下标为4,树状数组变为 1 1 0 1 0
4坐标前的前缀和为2(1+1),所以这样又有两个逆序对
每次都这样,最后便可以求出逆序对个数,因为树状数组可以用于快速求前缀和,便可以使用他来加快速度。
注:这只是思想,希望大家能去学习树状数组的写法,确实是个好东西。由于本人也刚起步,理解也只有这种水平了。
#include<bits/stdc++.h> using namespace std; struct Node { int num;//位置下标 int val;//值 }; Node a[100086]; int YHT[100086]; int n,m,ans=0,l,r; inline int read() { int ans = 0, f = 1; char ch = getchar(); if (ch == '-') f *= -1; while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { ans = ans * 10 + ch - '0'; ch = getchar(); } return ans * f; } int lowbit(int x)//树状数组模板 { return x&(-x); } void update(int x) { for(int i=x;i<=n;i+=lowbit(i)) { YHT[i]+=1; } } int query(int x) { int sum=0; for(int i=x;i>0;i-=lowbit(i)) { sum+=YHT[i]; } return sum; } bool cmp(Node a,Node b)//定义排序方法 { return a.val>b.val; } int main() { n=read(); for(int i=1;i<=n;i++) { a[i].val=read(); a[i].num=i; } sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++) { ans+=query(a[i].num);//求前缀和 update(a[i].num);//更新 } ans%=2; m=read(); while(m--) { l=read(); r=read(); int p=(r-l+1)*(r-l)/2; if(p&1) ans^=1; if(ans) printf("dislike\n"); else printf("like\n"); } }
小白写题解不易,大家如果觉得有帮助的话,点个赞哦,祝大家天天AC。