P6278 [USACO20OPEN]Haircut G

题目描述

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​ 。

题目分析

我们可以先预处理出j与j-1相比,会新增多少逆序对数。这也是这个题目的难点与重点。
首先我们可以让所有a[i]+1(方便后面的计算,不加也是可以的)。
我们可以发现:j=i时新增的逆序对的数量即为在长度为i-1的头发前面的头发中那些在j=i-1时还在生长的头发的数量。也就是说:第i根头发对答案的贡献应该在j=a[i]时开始,大小为a[k]>a[i]且k<i的数的个数。
我们可以反转a[i]的值(如:1变为n+1,2变为n……n+1变为1)反转后的值记为x[i]。 这样,我们只需要找出x[k]<x[i]且k<i的数的个数即可(这一步可以用树状数组来完成),这样问题就简化了很多。

代码如下
#include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
using namespace std;
const int N=1e5+5;
int n;
int a[N];
LL s[N],tr[N];		//s[i]记录j=i时能够多贡献的逆序对数
int lowbit(int x)	//树状数组的三个基本函数
{
    return x & -x;
}
void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr[i]+=c;
}
LL sum(int x)
{
    LL res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),a[i]++;	//使得a[i]的值变为1-n+1
    
    for(int i=1;i<=n;i++)
    {
        int x=n-a[i]+2;		//反转a[i]的值
        s[a[i]]+=sum(x-1);	//第i根头发的贡献是从j=a[i]时开始的,贡献值为小于x的数的和
        add(x,1);			//将x放入树状数组
    }
    LL ans=0;
    puts("0");
    for(int i=2;i<=n;i++)
    {
        ans+=s[i-1];		//每次加上当前时刻新增的逆序对即可
        printf("%lld\n",ans);
    }
    return 0;
}
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页