[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. 1≤a<b<c<d≤n
2. Aa<Ab
3. Ac<
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 解題思路:參考
題意為給定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;
}