1. 程式人生 > >UValive 4329 --樹狀陣列入門

UValive 4329 --樹狀陣列入門

白書上樹狀陣列的例題,剛好學完,正好熟悉一下樹狀陣列的常規操作。

對於每個i當裁判時的情況,設從a1到ai-1有ci個小於ai的數,則就有i-1-ci個比ai大的數,從ai+1到an有di個小於ci的數,就有n-i-di個比ai大的數,這樣,大數與小數配對,小數與大數配對,就有ci*(n-i-di)+di*(i-1-ci)種不同的組法,問題就變成求ci和di。

如何求ci和di呢?從0到ai的最大值1e5遍歷一遍,如果這個值存在(判斷在a[i]中是否出現),x[a[i]]=1,否則等於0,這樣,小於a[i]的值的個數就變為x[1]+x[2]+......+x[a[i]-1](仔細想想),那麼問題就變成如何求x[i]得字首和了,就可以用樹狀陣列求了。具體實現就是,輸入一個a[i],將它用add操作加入到樹狀陣列中,用sum求字首和。還有很多細節,看程式碼吧。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

typedef long long ll;
const int maxn=2e4+10;
const int maxm=1e5+10;
int n;

int c[maxn];
int d[maxn];//標號小於和大於i的,技能值比ai小的個數
int a[maxn];
int s[maxm];//樹狀陣列

int lowbit(int x)
{
    return x&-x;
}
int sum(int x)
{
    int ret=0;
    while(x>0)
    {
        ret+=s[x];
        x-=lowbit(x);//向左遞推,求和
    }
    return ret;
}

void add(int x,int d)
{
    while(x<maxm)//這裡注意一下,你求的是值的字首和
    {
        s[x]+=d;
        x+=lowbit(x);//向右遞推,修改元素
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(s,0,sizeof(s));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        //利用樹狀陣列求c[i]
        for(int i=1;i<=n;i++)
        {
            add(a[i],1);
            c[i]=sum(a[i])-1;//直接求c[i]的字首和,自己好好想象一下樹狀陣列是如何儲存字首和的
        }
        memset(s,0,sizeof(s));//記得初始化
        for(int i=n;i>=1;i--)
        {
            add(a[i],1);
            d[i]=sum(a[i])-1;//字尾和,滿足d[i]陣列的定義
        }
        ll ans=0;
        for(int i=2;i<n;i++)
        {
            ans+=c[i]*(n-i-d[i])+d[i]*(i-c[i]-1);
        }
        printf("%lld\n",ans);
    }
    return 0;
}