模版 動態 dp

2022-05-08 05:03:07 字數 3172 閱讀 4039

終於來寫這個東西了。。

lg 模版:給定 n 個點的數,點有點權, $ m $ 次修改點權,求修改完後這個樹的最大獨立集大小。

我們先來考慮樸素的最大獨立集的 dp

\[dp[u][0] = \sum_v max\\\dp[u][1] = val[u] + \sum_v dp[v][0]

\]現在我們就擁有了乙個 $ o(nm) $ 的做法,但是明顯它不優秀。

既然支援修改,我們可以考慮樹剖(或者lct),現在樹被我們剖成了重鏈和輕鏈。此時發現我們其實可以先對輕子樹算出 dp 值並且累計到當前根,再去算重鏈的影響,這樣有什麼好處呢?好處在於重鏈我們可以單獨算,這樣的話 dp 轉移就是連續的。同時,當你修改乙個點,它所影響的也僅僅是它到根的很多重鏈,不會影響到這路徑上虛兒子的貢獻。

但是如果按照原來的 dp 轉移,修改點權仍然很困難。為此定義了一種新的矩陣乘法,讓兩個矩陣相乘時的乘法改成加法,加法改成最大值,也就是:

\[(a\times b)_ = \text_k\ + b_\}

\]不難發現這樣定義矩陣乘法後矩陣乘法仍然滿足結合律。

然後我們考慮對於乙個點 $ u $ ,假設它已經算完了輕兒子的貢獻,計作 $ g[u][0/1] $ ,考慮從重兒子轉移,那麼這個點的轉移矩陣就是

\[\begin g[u][0]&g[u][0]\\g[u][1]&-\infin \end\times \begindp[u][0]\\dp[u][1]\end

\]為什麼呢?考慮 $ dp[u][0] $ 可以從什麼轉移過來,是由 $ dp[u][0] $ 和 $ dp[u][1] $ 中選擇較大的和 $ g[u][0] $ 加起來得到的,而 $ dp[u][1] $ 由 $ dp[u][0] + g[u][1] $ 得到,故最後乙個位置填 $ -\infin $。

寫到這裡感覺 lct 會比樹剖好寫的多,所以後面預設使用 lct 了。

然後我們考慮對每個鏈維護它的轉移矩陣的乘積。而 $ g $ 的維護就是對乙個鏈的頂端的父親更新 $ g $ 即可,體現在 lct 中就是乙個點維護它 splay 裡面的矩陣的積。注意這裡的矩陣乘法的順序,對乙個鏈做的時候應該從上乘到下,所以 pushup 的時候應該先左後中最後右。

如果我們需要得到乙個點的 dp 值,必須把它 access 並且 旋轉到根,類似 qtree v 的做法,不然它內部的值是假的(是子樹或者splay子樹內的乘積).

修改權值,比較簡單的方法是 lct 直接 旋轉到根 然後直接修改 val 和矩陣就可以了。

感覺比 red blue tree 好寫,不用拿 bst 維護虛兒子資訊。。(然後估計碼一天)

**還是很好看的(雖然調起來很煩)

#include "iostream"

#include "algorithm"

#include "cstring"

#include "cstdio"

using namespace std;

#define maxn 100006

#define max( a , b ) ( (a) > (b) ? (a) : (b) )

#define inf 0x3f3f3f3f

int n , m;

int w[maxn];

int head[maxn] , to[maxn << 1] , nex[maxn << 1] , ecn;

void ade( int u , int v )

struct mtrx

inline int re( )

inline mtrx operator * ( const mtrx& a ) const

};int ch[maxn][2] , fa[maxn] , dp[maxn][2];

mtrx g[maxn];

inline bool notroot( int u )

inline void pu( int u )

inline void rotate( int u )

//void rotate( int x )

void splay( int x )

}void access( int x )

}void pre( int u , int f )

g[u].in( u[dp][0] , u[dp][1] );

}int main()

}

其實就是板子題,每次詢問,強制選本質上就是權值 inf 強制不選擇就是 -inf

中間掛了幾次。。這題 longlong 得注意。。(矩陣返回值沒開longlong然後wa了兩發。。)

#include "iostream"

#include "algorithm"

#include "cstring"

#include "cstdio"

using namespace std;

#define maxn 400006

#define max( a , b ) ( (a) > (b) ? (a) : (b) )

#define inf (1ll<<60)

int n , m;

int w[maxn];

int head[maxn] , to[maxn << 1] , nex[maxn << 1] , ecn;

void ade( int u , int v )

struct mtrx

inline long long re( )

inline mtrx operator * ( const mtrx& a ) const

};int ch[maxn][2] , fa[maxn]; long long dp[maxn][2];

mtrx g[maxn];

inline bool notroot( int u )

inline void pu( int u )

inline void rotate( int u )

void splay( int x )

}void access( int x )

}void pre( int u , int f )

g[u].in( u[dp][0] , u[dp][1] );

}long long s;

void mdfy( int u , long long x )

int main()

}

數字dp模版(dp)

1 include 2 include 3 include 4 include 5 6using namespace std 78 intt 9long long dp 19 19 2005 10 long long l,r 11int shu 20 12 13long long dfs int l...

數字dp模版

int dfs int i,int s,bool e f為記憶化陣列 i為當前處理串的第i位 權重表示法,也即後面剩下i 1位待填數 s為之前數字的狀態 如果要求後面的數滿足什麼狀態,也可以再記乙個目標狀態t之類,for的時候列舉下t e表示之前的數是否是上界的字首 即後面的數能否任意填 for迴圈...

數字dp的模版

typedef long long ll int a 20 ll dp 20 state 不同題目狀態不同 ll dfs int pos,state變數 bool lead 前導零 bool limit 數字上界變數 不是每個題都要判斷前導零 計算完,記錄狀態 if limit lead dp po...