[好题][思维]Paimon Sorting 2021年ICPC南京站D

Paimon just invents a new sorting algorithm which looks much like bubble sort, with a few differences. It accepts a 1-indexed sequence A of length n and sorts it. Its pseudo-code is shown below.
Algorithm 1 The Sorting Algorithm
  Example
1: 2: 3: 4: 5: 6: 7: 8: 9:
function Sort(A) for i ← 1 to n do
for j ← 1 to n do if ai Swap ai and aj end if
end for end for
end function
◃ n is the number of elements in A ◃ai isthei-thelementinA
 If you don’t believe this piece of algorithm can sort a sequence it will also be your task to prove it. Anyway here comes the question:
Given an integer sequence A = a1, a2, · · · , an of length n, for each of its prefix Ak of length k (that is, for each 1 ≤ k ≤ n, consider the subsequence Ak = a1, a2, · · · , ak), count the number of swaps performed if we call SORT(Ak).
Input
There are multiple test cases. The first line of the input contains an integer T indicating the number of test cases. For each test case:
The first line contains an integer n (1 ≤ n ≤ 105) indicating the length of the sequence.
The second line contains n integers a1, a2, · · · , an (1 ≤ ai ≤ n) indicating the given sequence. It’s guaranteed that the sum of n of all test cases will not exceed 106.
Output
For each test case output one line containing n integers s1, s2, · · · , sn separated by a space, where si is the number of swaps performed if we call SORT(Ai).
Please, DO NOT output extra spaces at the end of each line or your solution may be considered incorrect!
standard input
3
5

2 3 2 1 5

3
1 2 3
1
1
standard output
0 2 3 5 7

0 2 4

0


题意: 题目给出了一段用于排序的代码,现在对于一个长度为n的数组,需要输出n个数,分别表示对前i个数进行排序需要进行多少次交换操作。

分析: 模拟一下题目中的算法,对于长度n的数组,将其排序需要n轮,第一轮单独处理,从第二轮开始,第i轮都是将最大的数换到第i个位置,且第i轮结束后,前i-1个数一定升序排列,这点如果能想到第i轮本质是取一个max序列循环右移就很好证明了。

借助树状数组可以O(nlogn)求出某一长度数组的交换次数,但是题目中要求的是n个长度不同的数组分别的交换次数,考虑到它们之间的关系,可以尝试从长度为i-1的答案推出长度为i的答案。具体做法是看前i-1个数的最大值,如果a[i]小于等于max[i-1],那么第一轮遍历时a[1]就一定是max[i-1]了,所以第一轮两数组操作相同,交换次数也相同,实际上第二轮到第i-1轮都完全一样,差别在于长度为i的数组还需要进行第i论遍历,此时前i-1个数都已经升序排列了,需要交换的次数就是前i-1个数中去重后大于a[i]的数个数,这个值用树状数组很容易获得。接下来就是a[i]大于max[i-1]的情况了,这种情况坑稍多些,当max[i-1]在前i-1个数中只出现一次时,其实两数组操作不同只在第一轮和最后一轮,长度为i的数组会在这两轮分别多一次交换操作,模拟一下就可以发现这点了,但是当max[i-1]出现次数大于1时就会有些问题,之前说在第二轮到第i-1轮中两数组操作相同是因为第一轮就将唯一的一个max[i-1]移动到了第i个位置,此时前i-1个数中不存在max[i-1]了,但是如果max[i-1]不唯一,此时中间轮次还会遇到max[i-1],在长度为i-1的数组中此时是不计算交换的,毕竟中间轮次遇到max[i-1]的时候第一轮次已经将第一个max[i-1]加入有序排列了,此时再遇到的max[i-1]就是第2或3 4 5次出现了,它肯定不会和前面的max[i-1]再发生交换了,而长度为i的数组中,第一轮是将比max[i-1]还大的a[i]加入了有序序列,中间轮次遇到max[i-1]的时候max[i-1]会和a[i]发生交换,并且之后每个轮次最后都会发生max[i-1]和a[i]的交换,也就是这种情况下交换次数是2+第二个max[i-1]后面的数个数。

具体代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define int long long
#define lowbit(x) x&(-x)
using namespace std;

int a[100005], c[100005], n, mx[100005], num[100005], pos[100005];

void add(int pos, int val){
	for(int i = pos; i <= n; i+=lowbit(i))
		c[i] += val;
}

int sum(int pos){
	int ans = 0;
	for(int i = pos; i >= 1; i-=lowbit(i))
		ans += c[i];
	return ans;
}

signed main()
{
	int T;
	cin >> T;
	while(T--){
		scanf("%lld", &n);
		for(int i = 1; i <= n; i++) num[i] = 0, pos[i] = 0;
		for(int i = 1; i <= n; i++){
			scanf("%lld", &a[i]);
			c[i] = 0;
			if(i == 1) mx[i] = a[i];
			else mx[i] = max(mx[i-1], a[i]);
		}
		int last = 0;
		add(a[1], 1);
		printf("%d", 0);
		num[mx[1]]++;
		for(int i = 2; i <= n; i++){
			if(a[i] > mx[i-1]){
				if(num[mx[i-1]] == 1) last += 2;
				else last += 2+i-pos[mx[i-1]];
			}
			else{
				last += sum(n)-sum(a[i]);
			}
			if(sum(a[i])-sum(a[i]-1) == 0)
				add(a[i], 1);
			if(a[i] == mx[i]){
				num[mx[i]]++;
				if(num[mx[i]] == 2) 
					pos[mx[i]] = i;
			}
			printf(" %lld", last);
		}
		puts("");
	}
	return 0;
}

你可能感兴趣的:(题解,思维,树状数组,算法)