1. 程式人生 > >[ACM] hdu 5147 Sequence II (樹狀陣列,字首和,字尾和)

[ACM] hdu 5147 Sequence II (樹狀陣列,字首和,字尾和)

Sequence II



Problem Description Long long ago, there is a sequence A with length n. All numbers in this sequence is no smaller than 1 and no bigger than n, and all numbers are different in this sequence.
Please calculate how many quad (a,b,c,d) satisfy:
1. 1a<b<c<dn
2. Aa<Ab
3. Ac<
Ad

Input The first line contains a single integer T, indicating the number of test cases.
Each test case begins with a line contains an integer n.
The next line follows n integers A1,A2,,An.

[Technical Specification]
1 <= T <= 100
1 <= n <= 50000
1 <= Ai <= n
Output For each case output one line contains a integer,the number of quad.
Sample Input 1 5 1 3 2 4 5
Sample Output 4
Source 解題思路:參考
http://www.cnblogs.com/pdev/p/4176056.html

題意為給定1~n的一個排列 用A[ ]陣列儲存,問有多少下標(a,b,c,d)四 元組滿足:

a<b<c<d 且 A[a] < A[b]   ,    A[c] < A[d].

題目中n的範圍是50000,O(n^2) 複雜度超時.....

思路為: 列舉c的位置,那麼每一次列舉中的方法數為 1到c-1中 (a,b)的個數 乘以  c到n中(c,d)的個數.累加起來即為答案。

1-c-1中(a,b)的個數相當於列舉b的位置,然後計算出b前面有多少數比A[b]小,該值要儲存下來,下一次列舉c的時候,該值再加上c-1前面有多少比a[c-1]小的數即為當前情況下1-c-1中(a,b)的個數,也就是b=c-1的時候,因為列舉b之前的情況已經算過了。

舉個例子:

當c列舉到第3時,b已經列舉完了1,2,並把當前(a,b)的方法數儲存,等c列舉到4時,剛才儲存的數加上b=3的情況,即為當前情況下(a,b)的方法數.

求當b=多少的方法數,也就是求b前面有多少個數比它小。

求當c=多少的方法數,也就是求c後面有多少個數比它大。

用樹狀陣列來做。本題n範圍50000,而且每個數都不相同很關鍵。所以我們就開闢n個位置,一開始每個位置都是0,其實每個位置不是0就是1,因為每個數只有一個。

比如數 1 3 2 4 5

一開始  c陣列 0 0 0 0 0

先統計,再輸入,因為計算a[i]前面有多少比它小的數,不包括它自己,而樹狀陣列計算和的時候,要包括它自己。

i=1,   樹狀陣列求和字首和 pre[1]=0 , 此時0 0 0 0 0  , 輸入1,變為  1 0 0 0 0

i=2,a[2]=3,要看 3前面有多少個數,也就是看c陣列的3個位置前面有多少個1,1代表已經輸入,發現1 0 0 0 0前三個數只有一個1,也就是pre[2]=1 (輸入的第二個數之前只有1個比它小的),輸入3以後,c陣列變為  1 0 1 0 0

i=3, a[3]= 2, 要看2前面有多少個數,也就是看c陣列前2個位置前面有多少個1,發現10100前兩個數中只有一個1,也就是pre[3]=1.

再求字尾和時,只要和上面一樣倒過來輸入就可以了。

程式碼:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=50010;
int a[maxn];
int pre[maxn],suf[maxn];//字首和 字尾和
//pre[i] 本題中表示輸入順序中第i個數之前有多少個比它小的數
//suf[i] 本題中表示輸入順序中第i個數之後有多少個比它大的數
//比如輸入 1 4 2 3 ,那麼pre[2]=1,因為4之前只有1比它小,suf[2]=0,因為4之後沒有比它大的數
int n;

///樹狀陣列部分
int c[maxn]; //第i個位置代表第i個數,c[i]=0代表該數未輸入,c[i]=1代表該數已經輸入

int lowbit(int x)
{
    return x&(-x);
}

void update(int i,int x)//在第i個位置上增加x
{
    while(i<=n)
    {
        c[i]+=x;
        i+=lowbit(i);
    }
}

int sum(int i) //c[1-i]之間的和
{
    int s=0;
    while(i>0)
    {
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}
///樹狀陣列結束

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++)
        {
            pre[i]=sum(a[i]);//統計a[i]之前有多少個比它小的數
            update(a[i],1);//在a[i]位置上加1,表示已經存在
        }
        memset(c,0,sizeof(c));
        for(int i=n;i>=1;i--)
        {
            suf[i]=(n-i)-sum(a[i]);//倒著輸入,第i個數後面有n-i個數,再看看這n-i個數中是不是存在比a[i]小的(即sum(a[i]))
            //,減去它們,就是a[i]後面所有比它大的
            update(a[i],1);
        }
        long long ans=0,dp=0;
        for(int i=1;i<=n-1;i++)//列舉c的位置
        {
            ans+=dp*suf[i];//dp表示輸入順序中第i-1個數之前有多少個數比第i-1個數小,在本題中也就是比b小的個數,i是c
            dp+=pre[i];
        }
        printf("%lld\n",ans);
    }
    return 0;
}