單調性-DP優化

zerojudge a146

題目敘述:

給定一個長度為N的序列A以及一個整數K,請找出所有長度為K的窗口極值(min&max)。

N, K <=10^6

Sliding window

1.對於每一個長度為K的窗口全部都掃一次

需(N-K+1)*K次

複雜度為O(NK)

 

作法

2.考慮單調性

以下先以Min的情況舉例

假設現在要計算以i為起始,那麼Ai有可能成為答案,對於所有j<i,Aj>Ai,Aj絕對不可能是i之後的答案

e.x. 

現在有陣列A 5 3 6 7, 若現在要算A[1]的答案,由於5>3,那之後5都不可能會是答案了。

所以考慮維護一個遞增的資料結構,當首元素過期時把它刪掉就可以了。

 

作法

//單調性,只會越來越小(除非過期
void find_min(int n, int k)
{
    deque<int> dq; //dq裡面紀錄的是位置
    for (int i=1; i<=n; i++)
    {
        while (!dq.empty() && a[dq.back()] > a[i]) //把大於a[i]且位置在i前面的都刪掉 
            dq.pop_back();
        dq.push_back(i);
        
        if (dq.front() == i-k) dq.pop_front(); //把過期的刪掉
        ans_mn[i] = a[dq.front()];
    }
}

而這樣的複雜度為O(N)

給你一個數列
求最長的嚴格遞增數列的長度 (不用連續

1 3 5 4 6

那麼這個數列可以是

1 3 5 6

1 3 4 6

LIS - (n logn)版

如何轉移

* 對於當前數字,看前面誰比她小而且長度最長

dp[i] = {length, from}

LIS - 普通版

int a[M];
for (int i = 1; i <= n; i++)
{
	for (int j = 1; j <= i; j++)
    {
    	if (a[i] >= a[j] && dp[j].ff + 1 > dp[i].ff)
        	dp[i] = {dp[j].ff + 1, j};
    }
}

先看題目

給你三個整數 nl 和 r

一個 長度為 n 的 ZigZag 陣列 定義如下:

  1. 每個元素的值都在區間 [l, r] 內。

  2. 相鄰的兩個元素 不能相等

  3. 不能出現 三個連續元素 形成嚴格遞增或嚴格遞減的序列。

請你返回 所有合法 ZigZag 陣列的總數,答案對 109+710^9 + 7 取模。

常用技巧-結合前綴和

直覺狀態定義

dp[i][k][0] //對於當前的第i項,當前數字為k的可能數

            // 0 / 1 代表上或者下

轉移式:

常用技巧-結合前綴和

dp[i][j][0] = sum_{k=l}^{j-1} dp[i-1][k][1]

Code

int dp[2005][2005][2] = {0}, sum = 0; // 第i個元素,結尾為j, k代表往上或下
int zigZagArrays(int n, int l, int r) {
        int P = 1e9 + 7;
        
        for (int i = l; i <= r; i++)
            dp[1][i][1] = 1, dp[1][i][0] = 1;    
        for (int i = 2; i <= n; i++)
        {
            for (int j = l; j <= r; j++)
            {
                for (int k = l; k < j; k++) // 往下
                    dp[i][j][0] =(dp[i][j][0] + dp[i - 1][k][1]) % P;
                for (int k = j + 1; k <= r; k++)
                    dp[i][j][1] =(dp[i][j][1] + dp[i - 1][k][0]) % P;
            }
            //cout << endl;
        }
        
        for (int i = l; i <= r; i++)
            sum = (sum + dp[n][i][0] + dp[n][i][1]) % P;
        return sum;
    }

複雜度O(n * m * m), where m = r-l

題目敘述:有M個煙火將在一維數列上綻放。

第i個煙火在時間Ti、於位置Ai綻放,其中1<=Ai<=N

類題:watching fireworks is fun

先按照綻放時間排序

定義$$dp_{i,j}$$為看完第i個煙火位於j的開心度

則我們只需關注他會從哪裡走到j,而這個哪裡我們先定為k

也就是 $$|k-j| \le  d* (T_i-T_{i-1})$$

解題

可以得到轉移式:

$$dp_{i, j} = max    dp_{i-1, k} + b_i - |a_i-j|$$

發現到右邊的$$b_i - |a_i-j|$$與轉移來源k並不相關

會發現與sliding windows很像

只需要找尋k的最大值(對k使用單調隊列)

也可以想成求長度為2*d*(Ti-Ti-1)+1 的sliding windows

解題

題目敘述:有 N 種物品,第 i 種重量為 wi,價值為 vi,且總共有 si 個。
求選出若干個物品使得總重量不超過 W 的情況下,最大總價值為何。

 

背包問題(有限背包)

以前的做法:定義dp[i]為重量為i時的價格,取

$$ max   dp_i =( dp_{i-w[x]} +v[x],   dp_i) $$

這在以前每項物品只有一個時複雜度為O(NW)

而現在題目每一項物品最多會有K(Si的最大值)個

這樣的做法複雜度會變成O(NWK)

背包問題(有限背包)

改良作法一:將同個種類的K個物品改成

$$ 1, 2, 4, 8....2^a, K-2^a$$個物品

可以套用回去0/1的背包問題,複雜度為O(NWlogK)

背包問題(有限背包)

改良作法二:定義dp(i, j)為前i個物品中重量為j的價格

$$ dp_{i, j} =  max_{0\le x\le s[i]}   (  dp_{i-1, j-x*w[i]} + x*v[i]  )  $$

我們將j-x*w[i] 訂為k,則上面的式子可改寫為

$$ dp_{i, j} =  max_{0\le \frac{j-k}{w[i]}\le s[i], j\equiv k (mod   w[i])}   (  dp_{i-1, k} + \frac{j-k}{w[i]}*v[i]  )  $$

由於我們的轉移來源變成k,可以在寫成下面的樣子來讓j/w[i]從轉移狀態移到前面

$$ dp_{i, j} =  \lfloor \frac{j}{w[i]} \rfloor * v[i]  + max_{0\le \frac{j-k}{w[i]}\le s[i], j\equiv k (mod   w[i])}   (  dp_{i-1, k} + \lfloor \frac{k}{w[i]} \rfloor*v[i]  )  $$

背包問題(有限背包)

可以發現,對於每個 j
只會取到與他差 w[i]∗x 的重量

也就是我們只要管同餘的重量!(即k)

接著就可以用剛剛教的單調隊列

 

背包問題(有限背包)

bitset

一個stl的資料結構

* bool 變數

* 但是儲存空間、位元運算比bool變數的表現好

 

位元運算:

<< 左移運算子

>> 右移運算子

例題

例題

一般情況下的複雜度是 O(\(n^2\))

但是透過bitset可以優化為O(\(\frac{n^2}{w}\))

ex 3 12

3 4 5

一開始dp = 1

1 | 1000 = 1001

1001 | 10010000 = 10011001

10011001 | 1001100100000 = 1001110111001

參考資料

題目連結

Desk

By yujuan

Desk

  • 289