腾讯校招2020 逆序对

题意:给 \(2^n\) 个数,Q 次询问每次给一个整数 \(q_i\),要求把这些数每 \(2^{q_i}\) 个分为一组,然后每组进行翻转,求每次操作后整个序列的逆序对个数。

\(0\le n\le 20,\ 1\le m\le 10^6\)


暴力肯定是没分的,玩几个数据可以发现一些性质。

3 2 1 4 5 有 3 个逆序对,如果把它整个翻转变成 5 4 1 2 3 有 6 个逆序对。想象一下构成逆序对的元素,把一个序列翻转实际上会使以前的顺序对变成逆序对。也就是说翻一个区间它的逆序对数量变成了顺序对数量。

考虑归并排序做法,如果不会归并排序可能理解不了这个做法。假设分治到了第 \(i\) 层,也就是说整个序列被我们分治成了 \(2^{i-1}\) 个小区间,我们记录一下合并那一层时逆序对的数量,然后再合并成一个有序区间。注意我们记录了第 \(i\) 层所有小区间合并时的逆序对数量。当以 \(2^q\) 分组重排的时候,实际上重排了第 \(q\) 层,同时也影响了第 \(n\) 层向上至第 \(q\) 层的答案。但是更上面的大区间的答案我们是没影响的。因为归并排序合并第 \(i\) 层用到的第 \(i+1\) 层的区间都是排序好了的,我们只记一层合并时的答案,第 \(q-1\) 到第 \(1\) 层合并时产生的答案还是没变。这个时候再通过区间逆序对数量 分治为 小区间的逆序对数和这一层的逆序对数 的思想,累加一遍 \(1-n\) 所有层的答案即可。

int t[MAXN];
void merge_sort(int *a, int l, int r, long long *cnt, int id) {
	if(l == r)return;
	int mid = (l + r) >> 1;
	merge_sort(a, l, mid, cnt, id - 1);
	merge_sort(a, mid + 1, r, cnt, id - 1);
	int p = l, q = mid + 1, cc = l;
	long long cur = 0;
	while(p <= mid && q <= r) {
		if(a[p] <= a[q])
			t[cc++] = a[p++];
		else
			t[cc++] = a[q++], cur += mid - p + 1;
	}
	while(p <= mid)
		t[cc++] = a[p++];
	while(q <= r)
		t[cc++] = a[q++];
	for(int i = l; i <= r; ++i)
		a[i] = t[i];
	cnt[id] += cur;
}

int a[MAXN], re[MAXN];
long long cnt[23], recnt[23];

int main(void) {
	int n, m, tot;
	io >> n;
	tot = (1 << n);
	for(int i = 1; i <= tot; ++i)
		io >> a[i];
	memcpy(re, a, sizeof a);
	reverse(re + 1, re + tot + 1);

	merge_sort(a, 1, tot, cnt, n);
	merge_sort(re, 1, tot, recnt, n);

	io >> m;
	long long ans;
	for(int q; m; --m) {
		io >> q;
		ans = 0;
		for(int i = 1; i <= q; ++i)
			swap(cnt[i], recnt[i]);
		for(int i = 1; i <= n; ++i)
			ans += cnt[i];
		printf("%lld\n", ans);
	}
	return 0;
}

posted @ 2020-10-24 16:42  Any%  阅读(99)  评论(0编辑  收藏  举报