1. 程式人生 > >哈理工OJ 1522 子序列的和(單調佇列)(dp)

哈理工OJ 1522 子序列的和(單調佇列)(dp)

子序列的和
Time Limit: 1000 MS Memory Limit: 32768 K
Total Submit: 289(61 users) Total Accepted: 71(43 users) Rating:  Special Judge: No
Description
輸入一個長度為n的整數序列(A1,A2,……,An),從中找出一段連續的長度不超過m的子序列,使得這個子序列的和最大。
Input
有多組測試資料,不超過20組測試資料 對於每組測試的第一行,包含兩個整數n和m(n,m<=10^5),表示有n個數,子序列長度限制為m,表示這個序列的長度,第二行為n個數,每個數的範圍為[-1000, 1000]。
Output
對於每組測試資料,輸出最大的子序列和,並換行。
Sample Input
3 1
1 2 3 3 2 -1000 1000 1

這道題不同於普通的子序列和最大的題目,這裡限制了子序列的長度,這裡應用一個單調佇列:顧名思義 單調佇列,即單調的佇列 。這裡求最大值,我們就需要一個單調遞增的佇列。而且要限制裡邊的元素的個數不能超過限制個數。

這裡我們分步詳解:這裡配合例項說:

3 2

3 -2 1

第一步:我們這裡求出序列區間和:31 2;sum陣列表示

第二步:向單調佇列中插入元素0;

第三步:向佇列中按序插入元素(當然是區間和的元素)(sum陣列內的元素)如果佇列中元素的個數超過了限制的個數,pop隊頭。至於插入的位子,不用多想也知道,既然是單調遞增函式,我們這裡可以從隊尾向前掃,如果找到了大於這個元素的地方,插入進去。這個時候注意,隊尾變成了這個數的位子。而且這裡為什麼要從佇列後邊pop元素呢,因為這些個元素用不上了。比如樣例中的資料,先進佇列的元素是3,同時當前子序列和最大是3.之後進來的元素是1.這裡我們sum陣列求的是區間子序列的和,也可以理解為除當前元素的之前所有元素和加上當前元素的和。如果這個和小於之前的和即sum【i】<sum【i-1】的時候說明了當前元素是個負數,不應該和前邊的區間和放在一個子序列當中,他應該單獨成一個序列(這裡應用到了普通的dp求最大子序列和的思想在裡邊。)所以佇列之前的元素,是直接拋棄就行了的,這也是為什麼我們沒有用C++的標頭檔案呼叫queue佇列的原因。

第四步:進行元素的值比較。

說的可能不是很清楚,大家可以對應程式碼細細琢磨,然後對應理解:

#include<stdio.h>
#include<string.h>
using namespace std;
struct node
{
    int val,weizi;
}queue[100010],tmp;
int sum[100010];
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            int k;
            scanf("%d",&k);
            sum[i]=sum[i-1]+k;//求區間子序列和
        }
        int s,e;//隊頭隊尾。
        s=0;
        e=-1;//初始化為-1;因為i=0的時候要0入隊
        int output=-0x1f1f1f1f;
        for(int i=1;i<=n;i++)
        {
            while(s<=e&&queue[s].weizi<i-m)//這是pop隊頭元素,對佇列元素的個數進行限制,並且達到了去舊的作用(舊了的元素沒用了,因為序列長度有限制,不可能讓序列和一直就這麼加下去。)
            {
                s++;
            }
            while(s<=e&&sum[i-1]<queue[e].val)//這是在pop隊尾元素,並且給當前要入隊的元素找到自己該呆的位子。
            {
                e--;
            }
            tmp.weizi=i-1;
            tmp.val=sum[i-1];
            queue[++e]=tmp;//入隊元素
            //printf("%d\n",sum[i]-queue[s].val);
            if(sum[i]-queue[s].val>output)//這是在計算限制長度的子序列區間和,可測試結果對應理解。
            output=sum[i]-queue[s].val;
        }
        printf("%d\n",output);
    }
}