模板 動態DP

2022-06-29 14:27:09 字數 2722 閱讀 5148

點此看題

動態 \(dp\) 的思路主要是用矩陣乘法加速 \(dp\),所以首先要知道矩陣乘法的擴充套件版:

\[c(i,k)=\max\

\]令人震驚的是上面這東西也滿足結合律,現在我們來證明一下,假設有三個矩陣 \(a,b,c\) 相乘,大小分別是 \(n\times m,m\times p,p\times q\),我們把最終某乙個位置上的值暴力展開:

\[(i,j)=\max_^ma(i,k)+\big(\max_^p b(k,l)+c(l,j)\big)

\]\[=\max_^m\max_^p a(i,k)+b(k,l)+c(l,j)

\]\[=\max_^p\big(\max_^m a(i,k)+b(k,l)\big)+c(l,j)

\]所以先乘 \(a,b\) 還是先乘 \(b,c\) 對答案沒有影響,結合律得證。

首先寫出暴力的 \(dp\) 柿子,設 \(f(u,0/1)\) 表示 \(u\) 這個點不選\(/\)選的最大權值,轉移:

\[f(u,0)=\sum\max(f(v,0),f(v,1))

\]\[f(u,1)=a(u)+\sum f(v,0)

\]先來考慮一下鏈怎麼做,我們構造乙個像這樣的轉移矩陣:

\[\left(\begin0&0\\a(u)&-\infty\end\right)\times\left(\beginf(v,0)\\f(v,1)\end\right)=\left(\beginf(u,0)\\f(u,1)\end\right)

\]然後要求根的 \(dp\) 值就直接把所有矩陣乘起來就行了,時間複雜度 \(o(n\log n)\)

那麼我們能不能把上面的做法搬到樹上呢?考慮把樹剖分成鏈然後套上面的做法,也就是用樹鏈剖分。每個點的轉移矩陣就針對他的重兒子來定義,但同時我們要考慮輕兒子對他 \(dp\) 值的貢獻,所以再定義 \(f'(u,0/1)\) 表示 \(u\) 不選\(/\)選,考慮 \(u\) 和 \(u\) 的所有輕兒子的最大值,那麼有如下轉移:

\[f(u,0)=f'(u,0)+\max\

\]\[f(u,1)=f'(u,1)+f(son,0)

\]寫成矩陣就是這個樣子的:

\[\left(\beginf'(u,0)&f』(u,0)\\f'(u,1)&-\infty\end\right)\times\left(\beginf(son,0)\\f(son,1)\end\right)=\left(\beginf(u,0)\\f(u,1)\end\right)

\]先考慮怎麼統計答案,我們找到根所在的那條重鏈,把所有轉移矩陣乘起來就行了。

再考慮如何修改,修改乙個點的點權只會對它的祖先產生影響。而且由於路徑上只有 \(o(\log n)\) 條輕邊,所以一共只需要改 \(o(\log n)\) 個矩陣,這部分可以看看**:

void modify(int u,int w)//把u點權改成w 

}

用乙個線段樹維護矩陣套上樹鏈剖分:\(o(2^3\cdot n\log^2 n)\)

#include #include using namespace std;

const int m = 100005;

const int inf = 1e9;

int read()

while(c>='0' && c<='9')

return x*f;

}int n,m,tot,cnt,f[m],a[m],id[m],fa[m];

int siz[m],son[m],num[m],top[m],bot[m],dp[m][2];

//top表示重鏈頭

//bot表示重鏈尾

//id表示線段樹上位置所對應的點

//dp表示初始的dp陣列

struct edge

}e[2*m];

struct matrix

matrix operator * (const matrix &b) const

void print()

}val[m],tr[4*m];

//線段樹部分

void up(int i)

void build(int i,int l,int r)

int mid=(l+r)>>1;

build(i<<1,l,mid);

build(i<<1|1,mid+1,r);

up(i);

}void upd(int i,int l,int r,int x)//修改x這個位置的矩陣

int mid=(l+r)>>1;

if(mid>=x) upd(i<<1,l,mid,x);

else upd(i<<1|1,mid+1,r,x);

up(i);

}matrix ask(int i,int l,int r,int l,int r)

//樹鏈剖分部分

void dfs1(int u,int p)

}void dfs2(int u,int tp)

else bot[u]=u;//如果沒有重兒子底部就是自己

for(int i=f[u];i;i=e[i].next) }

void modify(int u,int w)//把u點權改成w

}signed main()

dfs1(1,0);

dfs2(1,1);

build(1,1,n);

while(m--)

}

模板 動態 DP 動態樹分治

題面鏈結 include define ll long long define rg register using namespace std templateinline void read t x templateinline void write t x if x 0 x x,putchar ...

模板 動態規劃 數字dp

includeusing namespace std define ll long long int a 20 ll dp 20 20 可能需要的狀態1 20 可能需要的狀態2 不同題目狀態不同 ll dfs int pos,int state1 可能需要的狀態1 int state2 可能需要的狀...

luoguP4719 模板 動態 DP

我理解的動態dp 發現dp可以寫成矩陣的形式,因此用資料結構維護矩陣乘積。對於這道題,顯然有dp f 表示 x 的子樹中,x選 不選的最大點獨立集。f sum limits max f f f sum limits f a x 既然在樹上,就用樹剖或者lct解決,本質都是將樹拆成鏈,這裡用樹剖。設 ...