洛谷P6278题解

洛谷P6278题解

题目描述

Farmer John 由于对整理他难以整平的头发感到疲惫,于是决定去理发。他有一排 N N N缕头发,第 ii 缕头发初始时长度为 A i A_i Ai微米 ( 0 ≤ A i ≤ N ) 。 (0\le A_i\le N)。 0AiN理想情况下,他想要他的头发在长度上单调递增,所以他定义他的头发的“不良度”为逆序对的数量:满足 i < j i < j i<j A i > A j A_i > A_j Ai>Aj的二元对 ( i , j ) (i,j) (i,j)
对于每一个 j = 0 , 1 , … , N − 1 j=0,1,\ldots,N-1 j=0,1,,N1,Farmer John 想要知道他所有长度大于j的头发的长度均减少到 j时他的头发的不良度。

这道题真的卡了我好长时间,本来想拿这道题来学习树状数组求逆序对的,结果看了题解被绕了半天,不得不说,洛谷里这道题的题解说的都不清楚,不适合树状数组求逆序对的入门选手,下面写一篇详细一点的。

首先,这道题需要用到一个比较巧妙的思想:正难则反。既然正着减少不好想,那么放过来增加是好想的对吧。于是,这道题就变成了原来所有头发都是 0 0 0,每个时刻所有头发增加 1 1 1,第 i i i根头发最大长度为 A i A_i Ai,求每个时刻逆序对的个数。如果能求出逆序对,那么答案就是不断累加,因为当前时刻的逆序对总数 = 之前所有时刻的逆序对总数 + 该时刻新增的逆序对个数。

下面看怎么求这个逆序对。回顾最直白的逆序对我们怎么求的:先离散化(取排序后的下标),然后再用树状数组维护离散化后的数组。其实我的理解是,这个离散化无非是想找一种有序的标准,能够满足大的数先被加入树状数组(即越小的越大),这样的话我们访问前缀和就能获得比当前这个数大的数的数量。好了,回到这道题,我们怎么找这个标准呢?答案是用 n − A i n-A_{i} nAi,因为这个量表示第 i i i根头发与最大高度的差距,这个值越小,说明这根头发越大。于是这道题就解决了,我们用 s u m [ A i ] sum[A_{i}] sum[Ai]表示 A i A_i Ai时刻新增的逆序对数量,而求法就是标准的树状数组写法。

代码中之所以都+1是因为求逆序对的时候树状数组不能维护0,因此我们把整体范围都扩大1,不影响答案。

#include<cstdio>
#include<iostream>
#include<cstring>
#define N 500005
#define ll long long
using namespace std;
int n, a[N], c[N];
ll sum[N], ans;

int lowbit(int x) {
	return x & (-x);
}

void add(int x, int k) {
	for(int i = x; i <= n + 1; i += lowbit(i)) {
		c[i] += k;
	}
}

ll query(int x) {
	ll res = 0;
	for(int i = x; i; i -= lowbit(i)) {
		res += c[i];
	}
	return res;
}

int main()
{
	cin>>n;
	for(int i = 1; i <= n; ++i) {
		cin>>a[i];
		a[i]++;
	}
	for(int i = 1; i <= n; ++i) {
		int x = n - a[i] + 1;
		sum[a[i]] += query(x);
		add(x + 1, 1);
	}
	cout<<"0"<<endl;
	for(int i = 1; i < n; ++i) 
	{
		ans += sum[i];
		cout<<ans<<endl;
	}
	return 0;

看完代码后你也许又有一个疑惑:那 A A A数组肯定不会正好包含 1 ∼ n + 1 1 \sim n+1 1n+1,那些没出现过的 s u m sum sum值怎么办?

答案是没有问题。我们来考虑两个数 A i , A j A_i,A_j Ai,Aj,假设这本来是一个逆序对,即 i < j , A i > A j i \lt j, A_i \gt A_j i<j,Ai>Aj,那么对于任意的 k k k,如果说 A j < k A_j \lt k Aj<k,那么很显然 A i A_i Ai变成 k k k以后不影响这个逆序对,因此答案不变;如果 A j ≥ k A_j \ge k Ajk,那么答案确实会变化,但是按照我们反着做的做法, A j > k A_j\gt k Aj>k的情况其实会被 A j = k A_j =k Aj=k的情况覆盖,因此我们只需要考虑 A j = k A_j=k Aj=k的情况即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值