T2 [NOI Online 提高组]冒泡排序 题解

T2 [NOI Online 提高组]冒泡排序

这道题目实在是牛逼,主要是这个思想需要转变

T2 [NOI Online 提高组]冒泡排序 题解_第1张图片


暴力模拟肯定不行

我们仔细观察一下:
	我们需要在第一时间内知道当前这个数字前面有多少个数字比这个数字大
	说人话:知道在0~i-1下标有多少个数字比a[i]大
	这个可以考虑统计一下树状数组统计一下比他小的数字有多少个(记作tot),前面有i个数字,反向求出比他大的数字。
第二:
	我们这么想:如果需要排序,我们一定要找到规律
	举个粟子:假设这个序列1 5 4 3 2
	我们可以看见2前面有5 4 3,3前面有5 4。
	排序一次后:1 4 3 2 5,排序一次我们发现2前面比2大的数字少了1 只有4 3,同理3只有1次了
	我们就计算一下:前面具有比这个数字大的数字有几个,借助桶排序统计一下
	比如:下标为0的数字有n个,代表着n个数字前面比它大的数字有0个。
	下标为1的数字有m个,代表着m个数字前面比它大的数字有1个
	我们排完序列,每次排一次序列,只要是没有归位的数字都要减去1,就是说前面具有before[i]个大于这个数字的数字,每次排一次序列,before[i] = max(before[i]-1,0),i从0~n
第三:
	这个涉及到区间更新,我们考虑差分计算,所以考虑树状数组和差分配合使用,减少时间复杂度

#include<iostream>
#include<algorithm>
using namespace std;
int input[200005], before[200005], record[200005];
int n;
long long tree[200005];
inline int lowbit(int x)
{
	return x & (-x);
}
void update(int x, long long val)
{
	for (; x <= n; x += lowbit(x))
		tree[x] += val;
	return;
}
long long query(int x)
{
	long long res = 0;
	for (; x > 0; x -= lowbit(x))
		res += tree[x];
	return res;
}

int main()
{
	int m;
	cin >> n >> m;
	long long sum = 0; 
	for (int i = 0; i < n; i++)
	{
		cin >> input[i];
		before[i] = i - query(input[i]);
		//i代表前面有多少个数字,query(input[i])寻找到前面比它小的数字出现了几次
		sum += before[i];
		//计算出来总共的逆序对
		record[before[i]]++;
		//桶排序,下标代表前面有i个逆序对,有record[i]个
		update(input[i], 1);//把新来的数字加入进去
	}
	memset(tree, 0, sizeof(tree)); 
	update(1, sum);//让1为总数			   
	sum = 0;
	for (int i = 0; i < n; ++i)
	{
		sum += record[i];
		//sum代表着多少个数字,归位了
		update(i + 2, -(n - sum)); //我们得到的数字放入i+2的地方
		//n-sum意味着有多少逆序对归位了,可以这样子理解就是:
		//比如record[0] = 4,record[1] = 1,record[2]=1,n = 6
		//-(n - sum)代表下标为1,2都要减去1,当sum = 5代表着i = 1,排序一次
		//一个逆序对,大的数字已经到了后面去了,而下标为2代表这前面有两个大数字
		//一次排序后还有一个,用过这个-(n - sum)代表着少了几个逆序对
	}
	for (int i = 0, opt, x; i < m; i++)
	{
		scanf("%d%d", &opt, &x);
		x = min(x, n - 1); 
		if (opt == 1)
		{
			x--;
			if (input[x] < input[x + 1])
			{
				swap(input[x], input[x + 1]);
				swap(before[x], before[x + 1]);
				update(1, 1);//逆序对+1					
				update(before[x + 1] + 2, -1);//差分法需要对后面继续修改
				//保证前面大小和一样,这个是差分性质 
				before[x + 1]++;			
			}
			else
			{
				swap(input[x], input[x + 1]);
				swap(before[x], before[x + 1]);
				update(1, -1);			   
				before[x]--;		   
				update(before[x] + 2, 1); 
			}
		}
		else
			printf("%lld\n", query(x + 1)); 
	}
	return 0;
}

你可能感兴趣的:(算法,数据结构心得,刷题心得,算法,数据结构)