腾讯2020 逆序对(堆排,思维)

题目大意:

现在有2^n个数,我们总共有m次操作,每次操作我们可以让每2^k(k<=n)个数进行一次反转,现在问我们,每进行一次操作逆序对的个数是多少。

解题思路:

这里,我们需要发现,由于是每2^k进行一次反转,所以对2^(k+1)个数的逆序对是没影响的。具体如图:

腾讯2020 逆序对(堆排,思维)_第1张图片假设我们需要翻转绿色内的数字,那么红色圈圈内的跨越绿色圈圈的逆序对是没有任何影响的。

那么,我们从这里出发,考虑维护2^i个数中每的2^(i-1)个数之间的逆序对个数。翻转时候产生的贡献就是:

                                                                                     \sum_{i=1}^{(n)} cnt[i]

其中cnt[i]表示2^i内的2^(i-1)个数之间的逆序对个数。

腾讯2020 逆序对(堆排,思维)_第2张图片

比如我们在i=n时候,维护的就是上面红色箭头产生的逆序对个数。

另外,我们需要维护2^i个数的顺序对个数,以便反转时候用顺序对的个数替换成为逆序对的个数。

本题的难点:

首先,我们需要有一定的递归的思维考虑怎么算结果,例如这里,我们每次结果都是由2^i内的逆序对求和得出。

其次,我们需要求怎么求逆序对,这需要用到归并排序。

最后,关于敲代码的难点,在归并排序时候有比较多的指针,++顺序容易搞混。每次数组在填值的时候,mv[poi++]的用法可以学一下。

#include 
#define OPEN 0
#define int long long 
using namespace std;
vector arrmv;
vector cnt, max_c;
void merge_sort(int pos, int sz) {
	if (sz == 0)return; //递归的终点
	int nsz = (1 << (sz - 1));    //区间从中间切开
	int m = pos + nsz;             //另一边merge sort的起点
	merge_sort(pos, sz - 1);        
	merge_sort(m, sz - 1);
	vector tmp((1 << sz), 0);  //暂存merge sort结果
	int l1 = 0, l2 = 0;
	int tpoi = 0;
	while (l1arrmv[m + l2])l2++;
		else {
			int cnt1 = 0, cnt2 = 0;
			while (l1> n;
	int tot = 1 << n;
	arrmv.assign(tot, 0);
	cnt.assign(n + 1, 0); max_c.assign(n + 1, 0);

	for (int i = 0; i> arrmv[i];
	}
	for (int i = 1; i <= n; i++)max_c[i] = 1ll << (n + i - 2ll);    //2^i下的总共对数
    //包含顺序对,逆序对以及两两相等对.
    //注意移位时候的1ll表示long long 型
	merge_sort(0, n);
	int nu; cin >> nu;
	for (int ii = 0; ii> sz;
		int sum = 0;
		for (int i = 1; i <= n; i++) { 
			if (i <= sz)cnt[i] = max_c[i] - cnt[i]; //受影响的区间需要把顺序对和逆序对 对调
			sum += cnt[i];
		}
        assert(sum>=0);
		cout << sum << endl;
	}
	return 0;
}

 

 

你可能感兴趣的:(nowcoder,思维,逆序对,算法)