牛客練習賽38 D 題 出題人的手環 (離散化+樹狀數組求逆序對+前綴和)

連接:https://ac.nowcoder.com/acm/contest/358/D
來源:牛客網

html

出題人的手環
時間限制: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。

思路:
我的以爲仍是一個很不錯的題目。
首先,由於數值範圍比較大,先離散化一下。 (不會離散化的點此去學
其次先根據離散化後的數組標記下求一個前綴和,前綴和 sum[i] 維護的是 離散化後的數組(下面成爲新數組)中的1~i中累計的個數和,須要多開一個vis數組,
而後先根據樹狀數組來求出新數組的逆序對的總數。(具體求法和細節看code和註釋)
而後咱們把當前新數組中的第一個數放在最後一個位置,這樣對一個數組的逆序對總和的影響是 減去 2~n位置中小於a[1]的個數,加上2~n位置中大於a[1]的個數,
而這剛好能夠用咱們剛剛求得前綴和來解決。
而後每個調整後得數組的總和乘起來,記得取模就行了 (
算法學習(二)——樹狀數組求逆序數
細節見個人代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
#define sz(a) int(a.size())
#define all(a) a.begin(), a.end()
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define pii pair<int,int>
#define pll pair<long long ,long long>
#define gbtb ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define MS0(X) memset((X), 0, sizeof((X)))
#define MSC0(X) memset((X), '\0', sizeof((X)))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define eps 1e-6
#define gg(x) getInt(&x)
#define db(x) cout<<"==  "<<x<<"  =="<<endl;
using namespace std;
typedef long long ll;
inline void getInt(int* p);
const int maxn=1000010;
const int inf=0x3f3f3f3f;
/*** TEMPLATE CODE * * STARTS HERE ***/
int n;
ll a[maxn];
ll b[maxn];
ll tree[maxn];
ll vis[maxn];
ll sum[maxn];
const ll mod=1000000007ll;
int lowbit(int x)
{
    return x&(-1*x);
}
void add(int i)
{
    while(i<=maxn)
    {
        tree[i]+=1;
        i+=lowbit(i);
    }
}
ll query(int i)
{
    ll res=0;
    while(i>0)
    {
        res+=tree[i];
        i-=lowbit(i);

    }
    return res;
}
int main()
{
    gbtb;
    cin>>n;
    repd(i,1,n)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    sort(b+1,b+1+n);// 輔組數組的排序
    int cnt=unique(b+1,b+1+n)-b-1; // 去重
    repd(i,1,n)
    {
        a[i]=lower_bound(b+1,b+1+cnt,a[i])-b;  // 離散化掛的核心code
        vis[a[i]]++;// 標記
    }
    repd(i,1,n)
    {
        sum[i]=sum[i-1]+vis[i]; //  求前綴和,sum[i]表明1~的個數和。
    }
    ll tot=0ll;// 一個數組總的逆序對總和。
    repd(i,1,n)
    {
        add(a[i]);
        tot+=i-query(a[i]); // quert函數返回的是這個數組中小於等於a[i]的數量
        // 那麼i-quert 就是 數組中大於a[i]的數量,那麼就是逆序對的數量。
        //  樹狀數組 中維護的是一個單點修改,區間查詢的樹狀數組
        //  咱們add(a[i]) 就是向樹狀數組中增長一個a[i]出現的次數。
        //  query(a[i]) 查詢的就是 1 ~ a[i] 出現的數量總和。
        tot=(tot+mod)%mod;
    }
    ll ans=tot;
    for(int i=1;i<=n-1;i++)
    {
        tot=tot-(sum[a[i]-1])+n-sum[a[i]];
        // -(sum[a[i]-1]) 是減去比a[i]小的個數,即去掉了以a[i]爲左的逆序對,
        // +n-sum[a[i]]; 是加上 以a[i]爲右的逆序對,即加上數組中有多少個數大於a[i]
        tot=(tot+mod)%mod;
        ans*=tot;
        ans=(ans+mod)%mod;
    }
    cout<<ans<<endl;
    return 0;
}

inline void getInt(int* p) {
    char ch;
    do {
        ch = getchar();
    } while (ch == ' ' || ch == '\n');
    if (ch == '-') {
        *p = -(getchar() - '0');
        while ((ch = getchar()) >= '0' && ch <= '9') {
            *p = *p * 10 - ch + '0';
        }
    }
    else {
        *p = ch - '0';
        while ((ch = getchar()) >= '0' && ch <= '9') {
            *p = *p * 10 + ch - '0';
        }
    }
}

 

參考了這位大佬的題解:https://blog.csdn.net/qq_40655981/article/details/86547600ios

 

Maxn     詳細 X
網絡釋義
Maxn: 楠楠
int maxN: 亂數產生的最大數字
相關文章
相關標籤/搜索