【日常學習】【二分】【單調隊列優化線性DP】codevs3342 綠色通道題解

題目描述 Description

《思遠高考綠色通道》(Green Passage, GP)是唐山一中常用的練習冊之一,其題量之大深受lsz等許多oiers的痛恨,其中又以數學綠色通道爲最。2007年某月某日,soon-if (數學課代表),又一次宣佈收這本作業,而lsz還一點也沒有寫……

 

高二數學《綠色通道》總共有n道題目要寫(其實是抄),編號1..n,抄每道題所花時間不一樣,抄第i題要花a[i]分鐘。由於lsz還要準備NOIP,顯然不能成天寫綠色通道。lsz決定只用不超過t分鐘時間抄這個,因此必然有空着的題。每道題要麼不寫,要麼抄完,不能寫一半。一段連續的空題稱爲一個空題段,它的長度就是所包含的題目數。這樣應付自然會引起馬老師的憤怒。馬老師發怒的程度(簡稱發怒度)等於最長的空題段長度。

現在,lsz想知道他在這t分鐘內寫哪些題,才能夠儘量降低馬老師的發怒度。由於lsz很聰明,你只要告訴他發怒度的數值就可以了,不需輸出方案。(快樂融化:那麼lsz怎麼不自己寫程序?lsz:我還在抄別的科目的作業……)

輸入描述 Input Description

第一行爲兩個整數n,t,代表共有n道題目,t分鐘時間。

以下一行,爲n個整數,依次爲a[1], a[2],... a[n],意義如上所述。

輸出描述 Output Description

僅一行,一個整數w,爲最低的發怒度。

樣例輸入 Sample Input

17 11

6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5

樣例輸出 Sample Output

3

數據範圍及提示 Data Size & Hint

60%數據 n<=2000

100%數據 0<n<=50000,0<a[i]<=3000,0<t<=100000000

目測這篇博文之後會比較搶手,因爲這道題目沒有題解,百度搜索只會出現一個新浪博客,bing也只會搜出五個結果= =

這道題目目前只有codevs tyvj 以及SJTU ACM自己的題庫裏有···題目來自TSOI2007模擬賽 [2007-09-08]

這道題目的出題人(或者補充數據者)簡直是良心出題人阿有木有!!!五十個測試點!!!卡標算!!!

由於補充的十幾個點會卡掉n²的算法,需要用單調隊列優化···

我們首先用二分查找,對於每個mid檢查can()函數是否合法,即是否有一種方案滿足最長的空題段長度爲mid

如何檢查呢?

用f[i]表示在做第i道題的情況下,前i道題在合法狀況下(即空題段長度不超過mid)可能的所有方案中的最小時間(注意,空題段可能小於mid,這也是合法的

那麼轉移方程爲:f[i]=min(f[j])+a[i];

其中j的範圍是i-mid-1<=j<=i-1 這是因爲我們保證a[i]入選,且由於最大空時段爲mid,枚舉j應當從i之前不做mid個的前面那一道題開始

由於每個f[j]內的狀態必然是合法的最小值,也就是最長空時段不超過mid的最小值,而j又一定被選中(做這道題),最終方案也一定是合法的。


如果還不理解,那就紙筆模擬一下吧,樣例是很好的。


單調隊列由於之前沒有相關的博文,算是這個博客中第一次出現,在這裏解釋下,基本上引用的是一位江蘇省隊神犇的論文,然而並不知道名字···

例題:一個含有n項的數列(n<=2000000),求出每一項前面的第m個數到它這個區間內的最小值。

直接求解的複雜度是O(nm)

我們維護這樣一個隊列:隊列中的每個元素有兩個域{position,value},分別代表他在原隊列中的位置和,我們隨時保持這個隊列中的元素兩個域都單調遞增。


計算時,只要在隊首不斷刪除,直到隊首的position大於等於i-m+1,那此時隊首的value必定是f[i],因爲隊列是單調的!


將a[i]插入到隊列:首先,要保證position單調遞增,由於我們動態規劃的過程總是由小到大(反之亦然),所以肯定在隊尾插入。

又因爲要保證隊列的value單調遞增,所以將隊尾元素不斷刪除,直到隊尾元素小於a[i](由於要求最小值,大的自然用不到)。

每個元素最多值入隊出隊1次,複雜度O(n)

那麼放代碼

//codevs3342 綠色通道 二分+單調隊列優化DP
//copyright by ametake
//thanks to 量子糾纏 from NUST and wu_yihao from CSDN 
#include
#include
#include
using namespace std;

const int maxn=50000+10;
int n,t;
int a[maxn],f[maxn],q[maxn];


bool can(int x)
{
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    int l=0,r=0;
    q[r++]=0;
    for (int i=1;i<=n;i++)
    {
        while (l<=r&&q[l]=f[i]) r--;//刪除隊列尾部所有耗時比當前元素大的元素
        q[++r]=i; 
        /*
        //樸素的寫法是這樣的: 
        for (int j=max(i-x-1,0);j>1;
        if (can(mid))
        {
            r=mid;
        }
        else l=mid+1;
    }
    printf("%d",l);
    while (1);
    return 0;
}

——舊時茅店社林邊,路轉溪橋忽見




發佈了153 篇原創文章 · 獲贊 15 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章