【nowcoder】珂朵莉的数列 (树状数组 逆序对)

珂朵莉的数列

题意

珂朵莉给了你一个序列,有 n × ( n + 1 ) 2 \frac{n\times(n+1)}2 2n×(n+1)个子区间,求出她们各自的逆序对个数,然后加起来输出。

思路

对于一个逆序对(l,r)而言,它属于 ( n − r + 1 ) ∗ l (n-r+1)*l nr+1l个区间,理由如下,在这个逆序对左边有l-1个数,右边有n-r个数,它所属的区间可以有它左右各选取0个或多个数组成,因此它所属于的区间数为 ( n − r + 1 ) ∗ l (n-r+1)*l nr+1l

对序列中的每个数记录它的位置为 i d id id,如果对这个序列升序排序,则当 i < j 并且 i d [ i ] > i d [ j ] id[i] > id[j] id[i]>id[j] 时,(i,j)为一个逆序对。
也就是说,对于这个 i d id id序列中的每个数,我们要找到它后面有多少个数比它小。它后面所有比它小的数都可以和它组成一个逆序对。(当原序列中的数不重复时)

用树状数组可以很轻松的解决这个问题,但只知道个数我们没办法算区间数,考虑到当r固定时,对很多个可以和它组成逆序对的l,它们所组成的区间数和是可以提出公因式的:
( n − r + 1 ) ∗ ∑ l i (n-r+1)*{\sum}l_i nr+1li
所以干脆我们不找有多少个了,直接找比它小的数的和是多少。然后计算累加答案。

另外,当原序列中有相同的数时,我们在倒叙扫描时先遇到的应该是id大的数,要不然会计算错误,也就是说对 i d id id的排序按原序列升序,当原序列中的数相等时,按id值升序。

代码

#include

using namespace std;

const int N = 1e6+5;
const long long MOD = 1e18;

int a[N];
int id[N];
long long c[N];

void add(int x,int y)
{
    for(; x <= N; x += x&-x)
        c[x] += y;
}

long long ask(int x)
{
    long long ans = 0;
    for(; x; x-= x&-x)
        ans += c[x];
    return ans;
}
bool cmp(int x,int y)
{
    if(a[x]!=a[y])
        return a[x] < a[y];
    return x < y;
}
int n;
int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> a[i];
        id[i] = i;
    }
    sort(id+1,id+1+n,cmp);
    long long ans = 0 ,tans = 0;
    for(int i = n; i >= 1; --i){
        long long l = ask(id[i]);//找比它小的数的和
        long long r = id[i];
        ans += 1LL*(n-r+1)*l;
        if(ans>=MOD){//处理long long 溢出
            tans += ans/MOD;
            ans %= MOD;
        }
        add(id[i],id[i]);
    }
    if(tans)
        printf("%lld%018lld\n",tans,ans);
    else
        printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(Nowcoder)