P6278 [USACO20OPEN] Haircut G 题解
P6278 [USACO20OPEN] Haircut G 题解
服了,这题两个月之前做的,原题,考试考了我还没做出来,于是赶紧来补题解。
简化题意
给定一个长度为 \(n\) 的数组 \(a\)。对于 \(i = 0 \sim n - 1\),求进行 \(j = 1 \sim n\) 所有 \(a_j > i \rightarrow a_j = i\) 的操作后,数组 \(a\) 中逆序对的数量。
解题思路
看到逆序对,于是考虑可能与树状数组有关。
我们倒推,开一个空白数组 \(b\),假设是反过来操作 (在原题中相当于 FJ 去种头发),那么操作流程就变成了每一次 \(i\) 加一时,所有 \(b_j\) 也加一。当 \(b_j = a_j\) 时,\(b_j\) 就不会再增加。
这样的话,问题就变成了:对于每一个 \(j\),当 \(b_j\) 不再增加时,求 \(b_j\) 和前面所有数能组成的逆序对数量。这样的做法就和树状数组求逆序对很类似了。
具体的操作流程如下:先初始化树状数组,对于 \(i = 1 \sim n\) 执行修改 \(i\) 所对位置在树状数组中的值(\(+1\) 即可)。我们将 \(a\) 数组升序排序,然后循环 \(i = 1 \sim n - 1\),若对于当前的 \(i\) 有 \(a_i = i\),即这个数不会再增加了,我们就对于它计算能组成的逆序对数量,然后修改它在树状数组的值(同上,\(-1\) 即可)。最后注意,我们是先输出答案再操作,不然顺序就错了。
代码部分
#include <bits/stdc++.h>
using namespace std;
int n;
struct node
{
int v, x;
}a[100010];
long long tree[100010];
bool cmp(node a, node b)
{
if(a.v != b.v)
return a.v < b.v;
return a.x < b.x;
}
void add(int x, int c)
{
while(x <= n)
{
tree[x] += c;
x += x & (-x);
}
}
long long query(int x)
{
long long cnt = 0;
while(x > 0)
{
cnt += tree[x];
x -= x & (-x);
}
return cnt;
}
signed main()
{
cin >> n;
for(int i = 1;i <= n;i ++)
{
cin >> a[i].v;
a[i].x = i;
add(i, 1);//修改操作(+1)
}
sort(a + 1, a + 1 + n, cmp);//升序排序
long long ans = 0;
int pos = 1;
for(int i = 0;i < n;i ++)
{
cout << ans << "\n";
int j = pos;
while(i == a[j].v)//不会再增长了
{
ans += query(a[j].x - 1);//区间求和
add(a[j].x, -1);//修改操作(-1)
j ++;
}
pos = j;
}
return 0;
}
浙公网安备 33010602011771号