腾讯校招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;
}