Usaco2020 Open Harcut
Farmer John 由于对整理他难以整平的头发感到疲惫,于是决定去理发。他有一排 N 缕头发,第 i缕头发初始时长度为 Ai微米(0≤Ai≤N)。理想情况下,他想要他的头发在长度上单调递增,所以他定义他的头发的“不良度”为逆序对的数量:满足 i < j 及 Ai>Aj 的二元对 (i,j)。
对于每一个 j=0,1,…,N−1,Farmer John 想要知道他所有长度大于 j 的头发的长度均减少到 j 时他的头发的不良度。
(有趣的事实:人类平均确实有大约 105根头发!)
输入格式
输入的第一行包含 N。
第二行包含 A1 ,A2,…,AN。
输出格式
对于每一个 j=0,1,…,N−1,用一行输出 Farmer John 头发的不良度。
注意这个问题涉及到的整数大小可能需要使用 64 位整数型存储(例如,C/C++ 中的“long long”)。
样例
输入
5
5 2 3 3 0
输出
0
4
4
5
7
样例解释:
输出的第四行描述了 Farmer John 的头发长度减少到 3 时的逆序对数量。
A=[3,2,3,3,0]有五个逆序对:A1>A2 , A1>A5 , A2>A5 , A3>A5, 和 A4>A5 。
https://www.cnblogs.com/Andy-park/p/14069680.html
假设现在 John 不再需要为肆意生长的头发而烦恼了,而是成为了一个因秃头而抓狂的脱发人士。现在,我们把理发看成是生发,那么第 i 根头发的极限长度就是 ai.
头发从 0 开始生长,在到达极限长度之前,每秒生长 1 个单位长度。现在我们把题目中的 j 看成是时间,那么第 j 秒头发最长一定不会超过 j. 这不是正好符合题目限制吗?
显而易见的,第 0 秒的逆序对个数是 0. 那么,我们现在需要考虑的是第 j 秒新增的逆序对。如果第 i 根头发的最大高度 ai=j−1,说明第 j 秒它已经停止生长了。当 j−1 秒时,那些跟它比肩的小伙伴(指还未停止生长的头发)很可能借助从这一秒开始超过它。
也就是说,当满足 j<i,aj>ai 时,从第 ai+1 秒开始,i 和 j 为答案贡献逆序对。观察前半句话,这还是个求逆序对,可以用树状数组维护;对于后半句话,就涉及区间加法了。因为这题不需要多次询问,因此直接差分即可。总时间复杂度 O(nlogn).
首先我们把原题稍微变化一下,长度从最小值从0变成1,1变成2....
于是输入的
5 2 3 3 0
变成
6 3 4 4 1
在第1秒时,头发队列为1 1 1 1 1,逆序对为0
在第2秒时,头发队列为2 2 2 2 1,逆序对为4
在第3秒时,头发队列为3 3 3 3 1,逆序对为4
在第4秒时,头发队列为4 3 4 4 1,逆序对为5
在第5秒时,头发队列为5 3 4 4 1,逆序对为7
我们设所有头发在第0秒时,长度为0
到了第1秒,都长成1 1 1 1 1
对于第1根头发,它可以长到长度为6,也就是说它在1到6秒时
都是可以生长的,只有到第7秒才停止。也就是说如果在它左边
如果有大于它的数字x,这个x必须是在第7秒时才会超过它,从而形成
新的逆序对。于是我们可以对于每个数字a[i]统计出来在它左边有多少个数字是大于它的而且这个贡献值发生的时间为a[i]+1(注意这个贡献是新增加出来的)
a[i] 发生的时间 新增的贡献值
6 7 0--(在6左边没有数字大于它)
3 4 1--(在3左边有1个数字大于它)
4 5 1--(在4左边有1个数字大于它)
4 5 1--(在4左边有1个数字大于它)
1 2 4--(在1左边有4个数字大于它)
于是ans[1]=0
ans[2]=ans[1]+sum[2]=0+4=4
ans[3]=ans[2]+sum[3]=4
ans[4]=ans[3]+sum[4]=5
ans[5]=ans[4]+sum[5]=5+2=7
#include <iostream> #include <cstdio> #include <cstring> #define LL long long #define lowbit(x) (x & (-x)) using namespace std; const int N = 2333333; LL n, a[N], sum[N], t[N]; void add(LL x) { while(x <= n + 1) t[x]++, x += lowbit(x); } LL query(LL x) { LL res = 0; while(x) res += t[x], x -= lowbit(x); return res; } int main() { scanf("%lld", &n); memset(t, 0, sizeof(t)); memset(sum, 0, sizeof(sum)); for(int i = 1; i <= n; i++) { scanf("%lld", &a[i]), a[i]++; //因为长度有可能为0,所以统统加1 //5 2 3 3 0----6 3 4 4 1 sum[a[i] + 1] += query(n + 1) - query(a[i]); //sum[i]代表在第i个时间,会新增加的逆序对的对数 //query(n + 1) - query(a[i])用来统计在加入a[i]这个数字前 //已加了多少个比它大的数字 add(a[i]); } for(int i = 1; i <= n; i++) { sum[i] += sum[i - 1]; printf("%lld\n", sum[i]); } return 0; }