出题人的手环

题目链接:https://ac.nowcoder.com/acm/contest/358/D

链接:https://ac.nowcoder.com/acm/contest/358/D
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

出题人的妹子送了出题人一个手环,这个手环上有 n 个珠子,每个珠子上有一个数。
有一天,出题人和妹子分手了,想把这个手环从两个珠子间切开,并按顺时针顺序展开成一条链。
可以发现,这条链一共有 n 种可能性。求这 n 种可能性的逆序对数之积模 1000000007。

输入描述:

第一行一个数 n,表示珠子个数。
接下来一行 n 个数,以顺时针顺序给出每个珠子上的整数

输出描述:

一个数,表示答案。
示例1

输入

复制
4
1 3 2 3

输出

复制
24

说明

一共有 4 种方式:
1 3 2 3;3 1 3 2;2 3 1 3;3 2 3 1;
逆序对数分别为 1,3,2,4,积为 24。

备注:

n<=200000,-10^9<=珠子上的整数<=10^9。

个人思路:做到这题的时候,推出了规律,就是下一次可以在前一次的基础上减去小于第一个数的数,加上大于第一个数的数 但问题是怎么求得第一个环有多少个逆序对数,想了许久,发现怎么处理都要n*n 的复杂度,但是明显会
超时,所以这题写不下去了,之后看题解,都是用树状数组加上离散化的思想来做的,想想也是,只有用树log(n)的复杂度才能满足,但是这题怎么用树状数组呢?
    都知道树状数组是树形结构,可以求区间和 和 单点更新,但是我们这题和区间和有什么关系呢? 我们要求的是逆序对数,也就是一个区间内在一个数后面的数比它小 因为顺序是递增的 逆序就是递减的咯。
    我们怎么求区间内 后面的比它小的数的个数呢? 首先,把每个数在序列中从小到大排第几算出来,相等的话就并列排第几,排第几也就是该数在序列中第几小,然后这个顺序就可以当做树状数组的下标了,
    但是还有一个问题,我们要求的是在这个数后面比它小的数,而不是整个序列中比它小的数,所以按照原始顺序插入的同时计算后面有多少个比它小的,其实就是减去前面有多少个比它大的,这就是答案了。
具体看代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=200000+5;
const int mod=1000000007;
typedef long long ll;
ll n;
int a[maxn];//a[i]表示 原位置为i的数 在序列中从小到大排第几
int c[maxn];//树状数组存储结构
struct Node{
    int v,w;//v代表值 w代表位置
}node[maxn];
bool cmp(const Node a,const Node b)
{
    return a.v<b.v;
}
int lowbit(int x)
{
    return x&(-x);
}
void updata(int pos,int v)
{
    while(pos<=n)
    {
        c[pos]+=v;
        pos+=lowbit(pos);
    }
}
int getsum(int pos)//求有多少个数比它小
{
    int sum=0;
    while(pos>0)
    {
        sum+=c[pos];
        pos-=lowbit(pos);
    }
    return sum;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)//存储对应的值和所在的位置
    {
        cin>>node[i].v;
        node[i].w=i;
    }
    sort(node+1,node+1+n,cmp);//按大小从小到大排序
    a[node[1].w]=1;//最小的数显然是排第一
    int pos=1;
    for(int i=2;i<=n;i++)//存取每个数按从小到大排序 能排第几
    {
        if(node[i].v==node[i-1].v) a[node[i].w]=pos;//相等的话则为首个数的位置
        else a[node[i].w]=++pos;
    }
    ll ans=0;
    ll re=1;
    for(int i=1;i<=n;i++)
    {
        updata(a[i],1);//插入树状数组
        ans=(ans+(i-getsum(a[i])+mod))%mod;//i是原位置 a[i]是原位置的数所排大小  getsum(a[i])  该大小的数前面有几个数

    }
    re=(re*ans)%mod;
    ll ad;
    ll sub;

    for(int i=1;i<n;i++)//求出了第一种情况的,接下来就是 每一次在前一次的基础上 减掉小于第一个数的数 加上大于第一个数的数
    {

        ad=(n-getsum(a[i]));
        sub=getsum(a[i])-(getsum(a[i])-getsum(a[i]-1));//注意这里是 a[i]-1  而不是 a[i-1]  !!  我就错在这了
        ans=(ans-sub+mod+ad)%mod;
        re=(re*ans)%mod;
    }

    cout<<re<<endl;
}
当初的梦想实现了吗,事到如今只好放弃吗~
原文地址:https://www.cnblogs.com/caijiaming/p/10294552.html