今天刚看完逆序对,总结一下。菜鸟总结,若有错的地方,欢迎指出。
逆序对:设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
Example :求给出一个由n个数组成的序列,求出该序列 的逆序对总数目
暴力解法这里就不提了。
思路 归并排序:(即使序列存在相同元素,该算法也适用,且代码不用修改)
归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。在合并的过程中(设l<=i<=mid ,mid+1<=j<=h),
当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并
排序中的合并过程中计算逆序数.
代码实现:
#include <cstdio> #include <cstring> #define MAX 500000+10 #define LL long long using namespace std; int a[MAX], tmp[MAX]; LL ans; void Merge(int l, int m, int r) { int i = l; int j = m + 1; int k = l; while(i <= m && j <= r) { if(a[i] > a[j]) { tmp[k++] = a[j++]; ans += m - i + 1; } else { tmp[k++] = a[i++]; } } while(i <= m) tmp[k++] = a[i++]; while(j <= r) tmp[k++] = a[j++]; for(int i = l; i <= r; i++) { a[i] = tmp[i]; } } void Merge_sort(int l,int r) { if(l < r) { int m = (l + r) >> 1; Merge_sort(l,m); Merge_sort(m+1,r); Merge(l,m,r); } } int main() { int n; int i, j; while(scanf("%d", &n),n) { for(i = 0; i < n; i++) { scanf("%d", &a[i]); } ans = 0; Merge_sort(0,n-1); printf("%lld\n", ans); } return 0; }
思路 二叉树:(我们先说序列不存在相同元素的情况,存在相同元素的情况后面补充)
1,初始化所有节点对应区间为0;
2,优先插入最大值,我们假设当前插入位置为pos,更新pos对应的区间为1。在每次插入后统计在pos位置之前有多少个数,说白了就是求 1到pos-1 区间 的数值总和;
3,累加每次插入的统计值,即是所求的当前序列的逆序对总数目。
给出一个3,4,1,2的序列,若用上面的思路来模拟,则过程如下
步骤 |
插入值 |
插入位置 |
区间1 |
区间2 |
区间3 |
区间4 |
本次操作得到逆序对数目 |
0 |
—— |
—— |
0 |
0 |
0 |
0 |
0 |
1 |
4 |
2 |
0 |
1 |
0 |
0 |
0 |
2 |
3 |
1 |
1 |
1 |
0 |
0 |
0 |
3 |
2 |
4 |
1 |
1 |
0 |
1 |
2 |
4 |
1 |
3 |
1 |
1 |
1 |
1 |
2 |
最后我们累加每次操作得到的逆序对数目:0 + 0 + 2 + 2 = 4。So , 我们就求出了该序列的逆序对数目为4。
下面说下代码实现过程:
具体实现:我的想法是用结构体来记录序列里面的每个数值以及它们在序列里面的位置(第一个出现的在序列里面的位置为1......依次类推),然后用sort对结构体排序,排序优先数值大的。
该过程代码实现:
struct record { int val;//记录数值 int pos;//记录该数值在序列的位置 }num[]; bool cmp(record a,record b)//优先数值大的 { return a.val > b.val; }
下面就是总的代码实现了:
代码实现一 -->线段树:
#include <cstdio> #include <cstring> #include <algorithm> #define MAX 500000+10 #define LL long long using namespace std; int sum[MAX<<2];//记录当前节点对应区间的数值和 struct record { int val, pos; }num[MAX]; bool cmp(record a,record b) { return a.val > b.val; } void build(int o, int l, int r)//建树 { sum[o] = 0; if(l == r) return ; int mid = (l+r) >> 1; build(o<<1, l, mid); build(o<<1|1, mid+1, r); } int query(int o, int l, int r, int L, int R)//查询 区间[L,R]的数值和 { if(L == l && R == r) { return sum[o]; } int mid = (l+r) >> 1; if(R <= mid) return query(o<<1, l, mid, L, R); else if(L > mid) return query(o<<1|1, mid+1, r, L, R); else return query(o<<1, l, mid, L, mid) + query(o<<1|1, mid+1, r, mid+1, R); } void update(int o, int l, int r, int L)//更新 L { sum[o] += 1; if(l == r) return ; int mid = (l+r) >> 1; if(L <= mid) update(o<<1, l, mid, L); else update(o<<1|1, mid+1, r, L); } int main() { int n, i; LL ans;//记录逆序对数目 while(scanf("%d", &n), n) { build(1, 1 ,n);//建树 for(i = 0; i < n; i++) { scanf("%d", &num[i].val);//数值 num[i].pos = i + 1;//记录位置 } sort(num, num+n, cmp);//排序 ans = 0; for(i = 0; i < n; i++) { update(1, 1, n, num[i].pos);//插入 if(num[i].pos == 1)//位置为1不用算 因为它前面不会有数 continue; ans += query(1, 1, n, 1, num[i].pos-1);//加上区间[1,num[i].pos-1]的数值和 } printf("%lld\n", ans); } return 0; }
代码实现二 -->线段树 + lazy思想 :
#include <cstdio> #include <cstring> #include <algorithm> #define MAX 500000+10 #define LL long long using namespace std; int sum[MAX<<2]; struct record { int val, pos; }num[MAX]; bool cmp(record a,record b) { return a.val > b.val; } void PushUp(int o) { sum[o] = sum[o<<1] + sum[o<<1|1]; } void build(int o, int l, int r)//建树 { sum[o] = 0; if(l == r) return ; int mid = (l+r) >> 1; build(o<<1, l, mid); build(o<<1|1, mid+1, r); PushUp(o); } int query(int o, int l, int r, int L, int R)//查询 { if(L <= l && R >= r) { return sum[o]; } int mid = (l+r) >> 1; int res = 0; if(L <= mid) res += query(o<<1, l, mid, L, R); if(R > mid) res += query(o<<1|1, mid+1, r, L, R); return res; } void update(int o, int l, int r, int L)//更新 { if(l == r) { sum[o] += 1; return ; } int mid = (l+r) >> 1; if(L <= mid) update(o<<1, l, mid, L); else update(o<<1|1, mid+1, r, L); PushUp(o); } int main() { int n, i; LL ans; while(scanf("%d", &n), n) { build(1, 1 ,n); for(i = 0; i < n; i++) { scanf("%d", &num[i].val); num[i].pos = i + 1; } sort(num, num+n, cmp); ans = 0; for(i = 0; i < n; i++) { update(1, 1, n, num[i].pos); if(num[i].pos == 1) continue; ans += query(1, 1, n, 1, num[i].pos-1); } printf("%lld\n", ans); } return 0; }
代码实现三 -->树状数组:
#include <cstdio> #include <cstring> #include <algorithm> #define MAX 500000 #define LL long long using namespace std; int c[MAX]; int n; struct record { int val, pos;//记录数值 和 位置 }num[MAX]; bool cmp(record a,record b) { return a.val > b.val; } int lowbit(int x) { return x&(-x); } int sum(int x)//求和 { int s = 0; while(x > 0) { s += c[x]; x -= lowbit(x); } return s; } void update(int x)//更新 { while(x <= n) { c[x] += 1; x += lowbit(x); } } int main() { int i, j; LL ans;//统计总数目 while(scanf("%d", &n), n) { memset(c, 0, sizeof(c));//初始化 for(i = 0;i < n; i++) { scanf("%d", &num[i].val); num[i].pos = i + 1; } sort(num, num+n, cmp); ans = 0; for(i = 0;i < n; ++i)//每一次插入当前最大值 { update(num[i].pos); ans += sum(num[i].pos-1);//统计前面有多少个数 } printf("%lld\n", ans); } return 0; }
补充存在相同元素情况:
上面代码实现的是序列不存在相同元素的情况,若是存在相同元素怎么办?不用着急,只需限制一下插入顺序就ok了。(具体为什么会这样?自己思考思考吧)
修改代码如下(其余代码不用修改):
struct record { int val; int pos; }num[]; bool cmp(record a, record b) { if(a.val != b.val) return a.val > b.val; else return a.pos > b.pos; }
掌握的知识不够多,只能到此为止了。