emm隨便錯
樹形DP
例題
沒有上司的舞會
沒有上司的舞會
定義 \(f[x][0/1]\) 表示x這個點選或者不選能夠帶來的最大的快樂值。
因為每一個人的頂頭上司肯定不能和他一起選,也就是說,如果當前這個人選了,那么他的直接下屬不能選,但是當前這個人不選對快樂值沒有影響,也就是。
然后題目就做完了,一個 dfs 解決問題
[HAOI2009]毛毛蟲
題面自己看吧,簡化不動...
傳送門
我們令 \(f_x\) 表示以 x 為根的子樹內最大的毛毛蟲的大小,而且 x 為毛毛蟲的頭,那么 \(f_x\) 的轉移方程就很顯然了。
令 \(cnt_x\) 為 x 的兒子大小。
\(f_x = \max_{to \in son_x} f_{to} + 1 + \max(0, cnt_{to} - 1)\)
[ZJOI2007]時態同步
給你一棵樹,邊有邊權,然后問你每次可以將一條邊的邊權加一,
問你至少多少次操作之后,1 這個根節點到葉子節點的路徑長度都相同。
可以讓所有葉子節點同時發出信號,然后這些信號同時到達根節點。於是我們可以自下而上的進行維護,使得每一節點所有子節點的信號同時到達該節點。
我們從根節點開始搜索,搜索到葉子節點,回溯的時候進行維護,先維護節點的所有子節點到該節點最大邊權(邊權為葉子節點到同時到達它所需要時間)。然后維護答案,答案為最大邊權減去所有到子節點的邊權。然后維護父節點的邊權,父節點邊權為該節點子節點的 最大邊權+父節點到該節點的時間。然后就回溯,重復操作,到根節點為止。
換根法
也叫二次掃描法
基本思路是先指定一個根結點,然后第一次dfs求出根節點的權值,然后第二次dfs的時候可以搞一個轉移方程由根結點轉移過去。
STA-Station
給定一個 \(n\) 個點的樹,請求出一個結點,使得以這個結點為根時,所有結點的深度之和最大。
一個結點的深度之定義為該節點到根的簡單路徑上邊的數量。
設 \(dp_i\) 為第 i 個結點作為根節點時的深度之和,我們不妨令根結點為 1,然后進行第一次dfs,求出根結點為 1 時的深度之和,然后考慮如何轉移。
以樣例為例
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4
我們可以知道 \(dp_4\) 為 \(12\) 考慮如何換到5這個結點。
因為換成 5 的時候相當於 5 所在的子樹所有的結點的深度都減一,剩下的結點的深度都加一,所以我們令 \(siz_x\) 記錄 x 這個結點的子樹的大小,\(point\) 為所有子樹的大小,然后\(x, to\) 為 x 結點與 x 結點所到的 to( x 為 to 的父親結點)。
那么 \(point - siz_{to}\) 為除了以 to 為根的子樹之外的有多少個點。
Great Cow Gathering G
給你一棵樹,邊有邊權,讓你求出一個到其他所有點的路徑和最小的那個點的路徑和。
和上一個題差不多,就是每每條邊有邊權,也就是說我們可以通過讓 siz 數組維護的值改變一下來 AC 這道題。
考慮 siz 數組中存貯每個牛棚的以及他的子樹中,一共有多少頭牛,剩下的轉移就和上邊那個題差不多了。
Choosing Capital for Treeland
給你一棵樹,但是樹上的邊都是單向邊,問以 i 點為根的時候,為了能夠遍歷到每個點,需要修改多少條邊的方向。
建圖的時候按照雙向邊建圖,正向和反向邊分別標記,然后可以很輕松的求出 1 為根時的答案,轉移的時候就判斷 x 與 to 之間連的邊是正向邊還是反向邊就行了。
Nearby Cows G
傳送門
給你一棵 \(n\) 個點的樹,點帶權,對於每個節點求出距離它不超過 \(k\) 的所有節點權值和 \(m_i\)
我們用 \(f_{i,j}\) 表示距離 i 點為 j 的所有點的點權和,可以很容易得到下邊的遞推式
\(f_{i,j} = \sum f_{x,j-1}\),其中 x 為 i 的兒子結點,第一遍 dfs 的時候求出這個即可。
考慮如何第二遍 dfs 搞出其他點的 DP 值,可以發現 \(f_{x,j} = f_{i,j - 1}\) ,然后我們發現還有算重的部分,所以可以簡單容斥一下。
清北學堂 某題
樹點染色類
Cell Phone Network G
一開始的時候還以為和黑白染色差不多的那種,但是仔細一想就會發現不對勁。
我們設 \(f_{x,0/1/2}\) 分別為:
- \(f_{x,0}\) 表示 i 被自己覆蓋 的最小花費
- \(f_{x,1}\) 表示 i 被兒子覆蓋 的最小花費
- \(f_{x,2}\) 表示 i 被父親覆蓋 的最小花費
可以發現,x 自己覆蓋的時候,也就是 x 自己建信號塔的時候,他的兒子們建或不建都可,所以轉移方程就是 \(f_{x,0} += \min \{ f_{son,0}, f_{son,1},f_{son,2}\}\)。
可以發現,自己被兒子覆蓋的時候只需要被其中一個兒子覆蓋就可以了,所以轉移方程就是: \(f_{x.1} = f_{sx.0} + \sum \big(\min (f_{son,0}, f_{son,1})\big )\) 。
可以發現,他自己如果被父親覆蓋,說明他的兒子一定沒有被他自己覆蓋,所以狀態轉移方程就是: \(f_{x,2} += \min \{ f_{son. 0}, f_{son.1}\}\)。
樹形背包
選課
洛谷
簡化題面
一共有 n 門課,但是你只能選其中的 m 門,而且這些課中有的有依賴,比如學數學之前先學數數。
思路
不妨設 \(f_{now, j, k}\) 表示以 now 為根節點的子樹,考慮前 j 個節點選 k 門課的方案數
因為 1 號節點是根節點,顯然遞推起點 \(f_{now, 1, 1} = val_{now}\)
這樣很容易得到狀態轉移方程 \(f_{now,j,k} = \max \{f_{now,j-1,k},f_{son,所有節點數,l}+f_{now,j-1,k-l}\}\);
然后我們觀察等式兩邊的特點,哪些是我們已知的?
在對 now 求解前,我們至少已經處理完了前面的子樹,所以 \(f_{son,所有節點數,l}\)是可以直接用的,然后在處理第 j 個節點前,前 j-1 個節點是我們已經處理過的,所以 \(f_{now,j-1,k}\) 和 \(f_{now,j-1,k-l}\) 也不用考慮循環順序問題。
但是問題來了,這樣開三維數組不會炸空間嗎,也許本題不會,但是我們可以很顯然的發現,空間是可以優化的,只要稍稍改變循環順序即可,我要用到 j-1 的內容,都是滿足 l<k 的,所以倒着循環 \(k\) 這樣就可以使我們在一個數組中當前值和上面我們用到的值完全不影響。(類似 01 背包)
code
洛谷
基環樹DP
城市環路
傳送門
一個基環樹上的最大點權獨立集。
題目中告訴我們每個點有點權且任意相鄰的兩個點不能同時選取,而且整個城市可以看做一個 n 個點,n 條邊的單圈圖(保證圖連通)。
很明顯這是一個基環樹上最大權獨立集問題,和沒有上司的舞會類似。
我們可以根據基環樹的良好性質把這個問題拆分成兩個問題。
-
我們把在環上的點都看做一種根節點,不考慮它們連向環上的點,它可以連出一棵外向的子樹。在這棵樹上,我們可以做樹上的最大權獨立集問題。
-
在環上的點,把它們當做環形 DP 問題求解就好了。
大體思路就是這樣。因為是基環樹,所以第一步肯定是找環(媽媽我會tarjan)
這里介紹一種 dfs + toposort 的做法。
首先我們在加邊的時候記錄一下每個點的入度,顯然入度為 1 的點都是葉子節點。我們就從這些葉子節點開始 toposort 。
拓撲排序完成之后,會發現環上的點的入度都變成了 2。我們就找到第一個在環上的點,從這個點開始 dfs,只走當前入度為 2 的點,同時記錄每一個點,這樣就找到了所有環上的點。
然后就是樹上的最大點獨立集問題,就和沒有上司的舞會一樣,設 \(f_{x,0/1}\) 表示在以 x 為根的子樹內,x 這個點選或不選能夠達到的最大價值,顯然有以下轉移方程。
求出每個環上的點選或不選的最大權值之后,可以在環上進行環形 DP。
我們令 \(g_{i. 0/1}\) 為前 i 個點中,第 i 個點選或不選時能夠達到的最大價值,所以也有一個很顯然的式子。
初始化的時候要把 g 數組都賦成 -inf 然后需要欽定 \(g_1\) 選或不選的兩種情況,分別進行 DP。
時間復雜度 : \(\operatorname{O}(n)\)。
狀壓DP
棋盤類
[SCOI2005]互不侵犯
簡化題意
讓你在一個棋盤上放置 k 個棋子,每個棋子的八個方位不能有其他的棋子。
用 \(f_{i, j, s}\) 表示,第 i 行放置的方式為 j,放置了多少個棋子。
用 \(sit_x\) 表示 x 中 1 的個數,可以預處理,以及當前狀態下放置的國王的個數 gs 。
那么 \(f_{i,j,s} = \sum f_{i-1, k, s - gs_j}\)
轉移的條件就是:
- \(sit_j \ \& \ sit_k\) 及上下有重復的棋子。
- \((sit_j << 1) \& sit_k\) 及左上右下有重復的棋子。
- \(sit_j \& (sit_k << 1)\) 及左下右上有重復的棋子。
以上條件如果都不滿足,那么可以轉移。
code
loj
炮兵陣地
洛谷
簡化題意
也可以看成放旗子,但是只能把棋子放到空地上,而且棋子上下各兩個不能有其他棋子,左右各兩個不能有其他棋子。
用 \(f_{l,s,i}\) 表示上一行的狀態為 l,當前這一行的狀態為 s,現在是第 i 行時能放置的最多的炮兵數,那么轉移方程可以很容易的推出,\(sum_s\) 表示當前的狀態 s 中包含多少個 1。
\(f_{l,s,i} = max(f_{l,s,i}, f[fl,l,i-1]+sum_s)\)
下邊只需要判斷那里能放那里不能放就行了。
可以用一個數組 \(a_i\) 來表示第 i 行中,山的位置是 1,平地為 0。
判斷當前狀態有沒有在平原上可以直接 \(s \ \& \ a_i\) 就行了。
code
洛谷
Corn Fields G
洛谷
簡化題意
其實和上邊的問題都差不多,只是 prework 有些不懂而已。
** code **
洛谷
蒙德里安的夢想
AcWing
題意分析
rua,可以分割棋盤,但是我們發現分割棋盤之后會有好多長方形被攔腰折斷,所以可以沒被折斷的看做 0,折斷的看做 1,那么下一行中那些折斷的就必須看做 0, 剩下的可以是1 也可以是 0。
用 \(f_{i,j}\) 表示第 i 行的形態 為 j 時,前 i 行的分割方案的總數。
第 \(i-1\) 的形態 k 轉移到第 i 行的 j 形態,當且僅當:
- j 和 k 的 \(\&\) 結果是 0, 這保證了每個數字 1 下方必須是數字 0 ,代表繼續補全上比方豎着的長方形。
- j 和 k 的 \(\big|\) 每一段連續的 0 必須有偶數個,代表了橫着放的長方形。
我們可以 DP 求出 \([0, 2^M - 1]\) 內所有滿足“二進制表示下每一段都是連續的0有偶數個”的整數,記錄下來。
不難發現
code
AcWing
奇怪的狀壓
[AHOI2009]中國象棋
洛谷
簡化題意
在一個 \(n \times m\) 的棋盤上最多可以放置多少個炮,試任意的兩個炮不能相互打到。
感覺這也不像是一個狀壓DP,但是標簽上就是這么寫的..
可以發現,讓任意的兩個炮不能相互打到就是,任意的一行一列都不能有兩個炮。
讓 \(f_{i,j,k}\) 表示前 i 行中 j 列有一個炮,k 列有兩個炮的最大方案數。
可以發現,可以由一下狀態轉移而來。
- \(f_{i-1,j, k}\),可以一個都不放,那就是上一個的方案數。
- \(f_{i-1, j-1,k}\),從一個炮都沒有放的列轉移而來,因為有 \(m - (j - 1) - k\) 個沒有放任何炮的列,所以最后要乘上這個系數。
- \(f_{i-1,j+1,k-1}\),這個表示從 j+1 個放了一個炮的,在這些列中任意放上一個,讓放一個的變成放兩個的,因為那些列可以隨便取,所以要乘以 \(j+1\)。
- \(f_{i-1,j - 2, k}\), 當前這一行放兩個,都放到沒有放過的地方,這個時候沒有放過的地方有 \(m - (j - 2) - k\) 個,從這些中任取兩個,就是 \({m-(j - 2) - k}\choose 2\),乘上這個系數。
- \(f_{i - 1, j, k-1}\),當前這一行放兩個,放到同一列中,讓那一列變成兩個炮,因為當前沒放過的有 \(m - j -(k -1)\)。
- \(f_{i-1,j+2, k - 2}\),從原本有一個炮的列中任意選兩個,各放上一個炮,\({j+2} \choose 2\),乘上這個系數。
code
洛谷
DP 優化
單調隊列優化DP
loj 最大連續和
loj
簡化題意
給你 n 個數的序列,找出一段連續的長度不超過 m 的非空子序列,使得這個序列的和最大。
\(1 \leq n , m \leq 2e5\)
思路
有一個非常顯然的 DP,\(F_i = \max \Big\{ \sum_{j = i - k + 1}^i A_j \big| k \in [1,m]\Big\}\)
然而這樣做是 \(\operatorname{O}(nm)\) 的,並不優秀。
我們令 \(s_i = \sum_{j = 1}^i A_j\),則:
考慮用隊列來維護決策值 \(s_{i-k}\) ,每次只需要在隊首刪除 \(s_{i-k-1}\),在隊尾加入 \(s_{i-1}\),但是取最小值的操作還是需要 \(\operatorname{O}(m)\) 實現。
考慮在添加 \(s_{i-1}\) 的時候,設現在隊尾元素為 \(s_k\) ,由於 \(k < i-1\),所以 \(s_k\) 必然比 \(s_{i-1}\) 先出隊。若此時 \(s_{i-1} \leq s_k\),則 \(s_k\) 這個決策就永遠不會再以后用到,可以將 \(s_k\) 從隊尾刪除,此時的隊尾就形成了一個類似棧的一個東西。
同理,若隊尾的兩個元素 \(s_i\) 和 \(s_j\) ,若 \(i < j\)且 \(s_i \geq s_j\),則我們可以刪掉 \(s_i\),因為 \(s_i\) 永遠都不會用到了,此時的隊列中的元素構成了一個單調遞增的序列,即 \(s_1 < s_2 < \dots < s_k\)。
維護的時候只需要這么做就行了。
- 若當前隊首元素 \(s_x\),有 \(x < i - m\) , 則 \(s_x\) 出隊,知道隊首元素 \(x \geq i-m\) 為止。
- 若當前隊尾元素 \(s_k \geq s_{i-1}\),則 \(s_k\) 出隊,知道 \(s_k < s_{i-1}\) 為止。
- 在隊尾插入 \(s_{i-1}\)。
- 取出隊列中的最小值,更新一下答案。
代碼
code
Mowing the Lawn G
洛谷
簡化題意
給你一個 n,k 和 n 個整數 \(a_i\),從 a 中取出任意多個數,讓取出得數的和最大,並且相鄰的取出的數的個數不能超過 k 個。
思路
令 \(f_{i, 0/1}\) 表示以 i 這個數結尾, i 這個數取或者不取,能夠到達的最大價值。
容易得到下邊的轉移方程。
可以上邊那個式子可以轉化為 \(f_{i.1}= \max \Big\{ f_{j,0}- sum_j\Big\} + sum_i\)
式子上邊的那個 max 可以單調隊列優化。
code
洛谷
綠色通道
思路
讓最長的空段至少有多長,emm顯然可以二分答案。
可以二分一個最長的空段,check 的時候找到最小的時間。
令 \(f_i\) 表示前 i 個數中選取的最小價值。
易得 \(\displaystyle f_i = \min_{i-m \leq j < i} f_j + a_i\)
前邊那個 \(\min\), 可以單調隊列優化。
然后你就做完了,大水題,雙倍經驗就不需要二分了,比這個還水。
code
loj
[SCOI2010]股票交易
簡化題意
三個數字 T, maxP, w 分別代表,一共有 T 天,任何時候手中不得超過 maxP 張股票,買賣股票必須在上次買賣之后的 w + 1 天。
然后給你每天的 買入,賣出,最多能買,最多能賣的股票數,分別為 AP, BP,AS,BS。
思路
看看數據范圍,二位狀態沒多大問題,不妨就設 \(f_{i, j}\) 為第 i 天時,手中有 j 張股票的時候最多能賺的錢數。
轉移的話,有下邊幾種情況。
- 憑空買。
\(f_{i, j} = -ap_i \times j\big(j \in [0, as_i]\big)\),挺好理解,就是這一天買了 j 張股票,前邊幾天沒買也沒賣。
- 不買也不賣。
顯然就是上前一天的狀態, \(f_{i,j} = f_{i-1, j}\)。
- 在此基礎上買股票。
題目中說,兩次交易之間至少間隔 w 天,當前是第 i 天,也就是說可以從 i - w - 1 轉移過來,設第 i 天的時候一共有 j 個股票, 第 i-w-1 天的時候有 k 張股票,所以轉移方程就是:
as 為今天最多能買多少股票,應該很好理解。
- 在此基礎上賣股票。
和上一個差不多,但是賣股票,錢要加上 \(k - j \times bp_i\),所以轉移方程就是:
這樣你就有 60 分的好成績了。
考慮如何單調隊列優化,我們把上邊的式子拆開看看。
前邊那個 max 可以單調隊列優化掉,第四種轉移的時候同理。
code
loj
圍欄
簡化題意
rua
自己看
可以先將其按照 \(s_i\) 排序,使我們能夠按照一定的順序進行DP。
令 \(f_{i, j}\) 表示前 i 個工人中,已經塗了前 j 塊的最大價值,可以有不塗的。
有兩個非常明顯的轉移方程,即:
- \(f_{i, j} = f_{i-1, j}\), 第 i 個工人可以不干,所以可以承接上一個人的狀態。
- \(f_{i,j} = f_{i. j - 1}\),第 j 塊可以不塗嗯。
根據題意,該工匠的粉刷長度不能超過 \(L_i\),且必須粉刷 \(s_i\) 。不妨設一個 k ,令 \(f_{i-1, k}\), 為上一個人粉刷的最大價值,可以發現 \(j - l_i \leq k \leq s_i \leq j\),並且 \(j - k \leq L_i\) ,有下面的轉移方程。
化簡上邊的式子,按照這么一個思路,我們枚舉 \(j,k\) 的時候可以把帶着 i 的都看做一個常量,想辦法把 \(j,k\) 分開就行了。
這樣 max 里邊都是 k 的,外邊都是 j 的,只需要找出一個中間 max 最大就行了,可以發現,當 j 增大時上限不變,下限增大,不妨設兩個決策 \(k_1 < k_2 \leq s_i - 1\)。
因為 \(k_2\) 比 \(k_1\) 靠后,所以隨着 j 的增加,\(k_1\) 一定會比 \(k_2\) 更早的從 \([j-L_i,S_i -1]\) 中刪除,而且如果滿足 \(f_{i-1,k_1} - P_i \times k_1 \leq f_{i-1,k_2} - P_i \times k_2\),那就說明 \(k_1\) 就是一個無用的狀態。
你會發現這個東西像極了單調隊列,所以我們只需要維護一個 k 單調遞增,\(f_{i-1, k} - P_i \times k\) 單調遞減的隊列就行。
code
AcWing