洛谷P6186 [NOI Online #1 提高组] 冒泡排序 题解
Description
给定一个 \(1\) ~ \(n\) 的排列,接下来 \(m\) 次操作:
-
交换操作:给定 \(x\),将当前排列中的第 \(x\) 个数与第 \(x+1\) 个数交换位置。
-
询问操作:给定 \(k\),请你求出当前排列经过 \(k\) 轮冒泡排序后的逆序对个数。 对一个长度为 \(n\) 的排列 \(p_i\)
进行一轮冒泡排序的伪代码如下:
for i = 1 to n-1:
if p[i] > p[i + 1]:
swap(p[i], p[i + 1])
Solution
考虑每轮冒泡排序的影响,会把前 \(i\) 个数里最大的调到第 \(i\) 位上,所以前 \(i\) 个数中第 \(i\) 个数的逆序对个数会减 \(1\)。
那么 \(k\) 轮后逆序对的个数为 \(\sum_{i = 1}^{n}max(b_i-k,0)\),其中 \(b_i\) 表示以 \(i\) 为第二个数的逆序对的个数。
也就是 $$\sum_{b_i>k}b_i - \sum_{b_i>k}k$$
开两个树状数组,以 \(b_i\) 为下角标,一个维护 \(\sum b_i\),另一个维护 \(b_i > k\) 的个数。
Code
#include <iostream>
#include <cstdio>
#define int long long
#define swap(a, b) a ^= b ^= a ^= b
using namespace std;
const int N = 2e5 + 5;
int n, m, a[N], b[N];
struct tree
{
int c[N];
void init()
{
for(int i = 1; i <= n; i++)
c[i] = 0;
return;
}
void upd(int x, int y)
{
if(!x) return;
for(; x <= n; x += x & -x)
c[x] += y;
return;
}
int qry(int x)
{
int res = 0;
for(; x > 0; x -= x & -x)
res += c[x];
return res;
}
}c1, c2;
void update(int x, int y)
{
c1.upd(x, y), c2.upd(x, x * y);
}
int query(int l, int r)
{
int x = c1.qry(r) - c1.qry(l);
int y = c2.qry(r) - c2.qry(l);
return y - x * l;
}
signed main()
{
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++)
{
b[i] = i - c1.qry(a[i]) - 1;
c1.upd(a[i], 1);
}
c1.init();
for(int i = 1; i <= n; i++)
update(b[i], 1);
while(m--)
{
int op, x;
scanf("%lld%lld", &op, &x);
if(op == 1)
{
update(b[x], -1), update(b[x + 1], -1);
if(a[x] > a[x + 1]) b[x + 1]--;
else b[x]++;
swap(b[x], b[x + 1]), swap(a[x], a[x + 1]);
update(b[x], 1), update(b[x + 1], 1);
}
else
{
if(x >= n)
{
puts("0");
continue;
}
printf("%lld\n", query(x, n));
}
}
return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$