數串問題(歸併排序、前綴和變體後綴和)

Description
給定一個數串,數串的長度爲 n ,現在將一個子串的每個數字之和定義爲該子串的數串和,請你求出數串中有多少個子串的數串和爲正數。
Input
第一行一個數 n ,表示數串的長度。第二行一共 n 個數,表示數串中的每個數輸出就一個數,表示數串中有多少個子串的數串和爲正數。
Output
一個正數即答案。
Sample Input 1
3
8 -9 2
Sample Output 1
3
Hint
對於100%的數據: n ≤ 100000,所有數之和在int範圍之內
Time Limit
1000MS
Memory Limit
256MB

分析:
題中所述數串和,其實就是區間和。時空複雜度最小的求區間和的方法就是求前綴和序列,前綴和中每一項sum[i]表示a1~ai的和。故 ai ~aj的和爲sum[j]-sum[i-1],那麼數串ai ~aj之和大於0等價於sum[j]-sum[i-1]>0。觀察這個式子,既然 j>i,則必有 j>i-1,又sum[j]>sum[i-1],所以sum[j]和sum[i-1]是一對順序對
歸併排序可以求逆序對,用來求順序對也是換湯不換藥,只要把排序規則換爲降序,當左子序列頭指針元素小於右子序列頭指針元素時,就是出現順序對的時候。另外,爲了表示整個序列和時與其他數串和表示統一,要用sum[0]=0表示沒有加序列任何一個元素,sum[n]-sum[0]即爲整個序列的和。
那如果想通過求逆序對算這題可以嗎?答案是可以。既然能求前綴和,爲什麼不能求後綴和呢?分析步驟同上,數串ai ~aj之和大於零等價於sum’[i]>sum’[j+1],即sum’[i]與sum’[j+1]爲逆序對。爲了便於理解,特畫後綴和序列示意圖:
在這裏插入圖片描述對了,這題還有個,雖然所有輸入數據都在int範圍,序列所有元素的和也在int範圍,但是答案(正數串和)的範圍不一定在int範圍,本人就因爲沒開long long int記錄答案而導致WA。

參考代碼:

#include<stdio.h>

int n;//序列長度
int a[100002]={0};//原序列
long long int ans=0;//答案計數,開long long以防萬一
int temp[100002]={0};//歸併排序的道具
//將原序列轉化爲後綴和序列
inline void change()
{
    for(int i=n;i>0;i--)
    {
        a[i]+=a[i+1];
    }
}
//對後綴和序列歸併排序,順便求逆序對數目
void merge_sort(int left,int right)
{
    if(left==right) return;

    int mid=left+((right-left)>>1);
    merge_sort(left,mid);
    merge_sort(mid+1,right);

    for(int i=left;i<=right;i++)
       temp[i]=a[i];
    
    int i1=left,i2=mid+1;
    for(int cur=left;cur<=right;cur++)
    {//排序規則爲升序,誰小先選誰
        if(i1>mid) a[cur]=temp[i2++];
        else if(i2>right) a[cur]=temp[i1++];
        else if(temp[i1]<=temp[i2])
                a[cur]=temp[i1++];
        else if(temp[i1]>temp[i2])//出現逆序對
                a[cur]=temp[i2++],ans+=mid-i1+1;
    }
    return;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
       scanf("%d",a+i);
    change();
    //令sum[n+1]=0,sum[1]-sum[n+1]表示整個序列的和
    //故雖然序列內容在1~n,區間卻必須寫1~n+1
    merge_sort(1,n+1);
    printf("%lld",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章