SCU-4593(代码部分来自泥煤DL)冒泡排序的交换次数来计算逆序数对数《挑战程序设计竞赛》(日译)P178(博主看了好久都没看懂)
#include"stdafx.h"
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int bit[maxn];
int n;
void init() {
memset(bit, 0, sizeof(bit));
}
void add(int i, int k) {
while (i <= n) {
bit[i] += k;
i += i & -i;
}
}
int sum(int i) {
int s = 0;
while (i > 0) {
s += bit[i];
i -= i & -i;
}
return s;
}
int main()
{
int T;scanf("%d", &T);
while (T--) {
init();
scanf("%d", &n);
int cnt = 0;
for (int i = 1;i <= n;i++) {
int t;scanf("%d", &t);
cnt = (cnt + i - 1 - sum(t - 1)) & 1;//取二进制数的末位,末尾是1则为奇数,若为奇数则Alice赢,在其后的讲解中暂时不考虑&1,因为这一部分是根据本题来的,而和逆序数对数无关
add(t, 1);
}
printf(cnt & 1 ? "Alice\n" : "Bob\n");
}
return 0;
}
P178(抄写)
题意:
给定一个1~n的排列a0,a1,…an-1,求对这个数列进行冒泡排序所需要的交换次数(冒泡排序是每次找到满足ai>ai+1的i,并交换ai和ai+1,直到这样的i不存在为止的算法)。
限制条件:1<= n<= 100000
输入:
n=4, a={3,1,4,2}
输出:
3
冒泡排序的复杂度是O(n2),所有无法通过模拟冒泡排序的过程来计算需要的交换次数。不过我们可以通过选取适当的数据结构来解决这个问题。
首先,所求的交换次数等价于满足i<j,ai>aj的(i,j)数对的个数(这种数对的个数叫做逆序数)。而对于每一个j,如果能够快速求出满足i<j,ai>aj的i的个数,那么问题就迎刃而解。我们构建一个值得范围是1~n的BIT,按照j=0,1,2,…,n-1的顺序进行如下操作。
*把j-(BIT查询得到的前aj项的和)加到答案中
*把BIT中aj位置上的值加1
对于每一个j,(BIT查询得到的前aj项的和)就是满足i<j,ai<=aj的i的个数。因此把这个值从j中减去之后,得到的就是满足i<j,ai>aj的i的个数。由于对于每一个j的复杂度是O(logn),所以整个算法的复杂度是O(nlogn)。
typedef long long ll;
//输入
int n,a[MAX_N];
//这里省略了BIT部分的代码
void solve(){
ll ans=0;
for(int j=0; j<n;;j++){
ans+= j-sum(a[j]);
add(a[j],1);
}
printf("%lld\n",ans);
}
SCU4593就是对P178的应用,但是P178特别难懂,所以可以直接用SCU4593的代码来理解这个办法。
首先要理解sum函数的含义,是计算前i项的和;而add函数的含义是在位置i加上x。
然后我们来举一个例子
一个有八个数字的数列:5,6,3,4,7,8,1,2
一开始都初始化为0
之后读入了5(i=1)cnt=cnt+i-1-sum(t-1)=0+1-1-sum(4)=0
这时可以知道BIT线段树中每一个位置存入的是每个数出现次数,且是按出现先后顺序存的,暂时未出现的就是0,所以这时我们可以理解了为什么这个方法可以求出来满足位置在某数前(通过按顺序读入每个数的出现次数,一开始都是0来实现)且比某数数值小(根据sum函数的定义,sum求前a[j]项的和,即保证前面都是比本数数值小的其他数的出现次数)的数的个数。
于是用前面一共出现几个数j减去前面比本数数值小的的数字个数sum(t-1)就是出现在本数前面且比本数数值大的数字个数,这些数字与本数组成j-sum(t-1)个逆序数对。
之后读入了6,暂时无逆序数(i=2)cnt=cnt+i-1-sum(t-1)=0+2-1-sum(5)=0
之后读入了3(i=3),sum(2)=0;cnt=cnt+i-1-sum(t-1)=0+3-1-sum(2)=2;
表示现在有两对逆序数
之后读入了4(i=4),sum(3)=1;cnt=cnt+i-1-sum(t-1)=2+4-1-1=4;
现在有四对逆序数
以下同理可得不再赘述,不过和SCU4593的唯一区别就是该题中只关心逆序对数的奇偶,所以每一步都只保存cnt是奇数还是偶数。