[NOI Online #1 提高组]冒泡排序
Luogu P6185 NOI Online #1 冒泡排序
首先定义\(f(i)\)表示序列中\(i\)位置前有多少个比他大的数。
根据冒泡排序的过程很容易发现一些\(f(i)\)的特点:
- 每经过一轮冒泡排序,由\(f(i)\)构成的序列就会整体减一并且向左移动一位。
- 已经为\(0\)的\(f(i)\)不会发生变化。
举一个例子。
原序列:
\(a_i\) | 3 | 5 | 4 | 2 | 1 |
---|---|---|---|---|---|
\(f(i)\) | 0 | 0 | 1 | 3 | 4 |
第一轮排序:
\(a_i\) | 3 | 4 | 2 | 1 | 5 |
---|---|---|---|---|---|
\(f(i)\) | 0 | 0 | 2 | 3 | 0 |
第二轮排序:
\(a_i\) | 3 | 2 | 1 | 4 | 5 |
---|---|---|---|---|---|
\(f(i)\) | 0 | 1 | 2 | 0 | 0 |
第三轮排序:
\(a_i\) | 2 | 1 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(f(i)\) | 0 | 1 | 0 | 0 | 0 |
相信到了这里规律已经很明显了。
一个小结论:如果一个位置的逆序对为\(k\),那么它在第\(k\)轮时的对答案的贡献就消失了。
那么可以这么想,考虑用树状数组维护第\(k\)轮排序的逆序对个数,遇到操作\(1\)时做对应的修改就可以了。
接下来就在于,应该如何去维护呢?
我们知道交换只会影响一个位置的逆序数,那么直接修改就可以了。
//代码缩进被VSCode强行格式化了……
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
long long ans, tree[200005], b[200005], p[200005], cnt[200005], t, k, n, m;
long long lowbit(long long x)
{
return x & -x;
}
void add(long long x, long long val)
{
for (long long i = x; i <= n; i += lowbit(i))
tree[i] += val;
}
long long query(long long x)
{
long long ret = 0;
for (long long i = x; i > 0; i -= lowbit(i))
ret += tree[i];
return ret;
}
int main()
{
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &p[i]);
add(p[i], 1);
b[i] = query(n) - query(p[i]);
ans += b[i];//记录初始逆序数。
cnt[b[i]]++;
}
memset(tree, 0, sizeof(tree));
add(1, ans);
int tmp = 0;
//这里把第一轮视为原序列,第二轮实际上是冒泡排序第一轮。
for (int i = 2; i <= n + 1; i++)
{
tmp += cnt[i - 2];//这一些不需要减,因为贡献已经为0了
add(i, -(n - tmp));//其他的贡献全都要-1
}
for (int i = 1; i <= m; i++)
{
scanf("%lld%lld", &t, &k);
if (t == 1)
{
if (p[k] < p[k+1])
{
add(1, 1);
b[k]++;
add(b[k] + 2, -1);
}
else
{
add(1, -1);
b[k + 1]--;
add(b[k + 1] + 2, 1);
}
swap(p[k], p[k + 1]);
swap(b[k], b[k + 1]);
}
else
{
k = min(n - 1, k);
printf("%lld\n",query(k + 1));
}
}
return 0;
}