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;
}

posted on 2026-09-05 22:38  Leo_29  阅读(4)  评论(0)    收藏  举报

导航