洛谷P4719 動態DP模板

2022-08-13 22:15:27 字數 3121 閱讀 6866

一棵 \(n\)個點的樹,點帶點權。

\(m\) 次操作,每次操作給定 \(x,y\) ,表示修改點 \(x\) 的權值為 \(y\)。

每次操作後求出這棵樹的最大權獨立集的權值大小。

\(n,m \leq 10^5\)

首先有乙個 \(o(nm)\) 的 \(dp\) :

設 \(f[u][0/1]\) 分別表示以 \(u\) 為根的子樹中,\(u\) 不選/選 的最大獨立集權值大小。

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

f[u][1]=val[u]+\sum\limits_ f[v][0]

\]顯然超時。考慮如何優化。

注意到每次只修改乙個點,也就是說只有該點到根節點的路徑上的點的 \(dp\) 值有變化。

這並沒有什麼卵用,如果是鏈就廢了。但這提示我們考慮樹剖(神邏輯…)

設 \(g[u][0/1]\) 表示只考慮所有輕兒子時的 \(dp\) 值。

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

g[u][1]=val[u]+\sum\limits_ f[v][0]

\]設 \(v\) 為 \(u\) 的重子,那麼

\[f[u][0]=g[u][0]+max(f[v][0],f[v][1]) \\

f[u][1]=g[u][1]+f[v][0]

\]這可以寫成廣義矩陣乘法形式(+變成 \(max\),乘變為+):

\[\begin

\left(

\begin

g[u][0]& g[u][0] \\

g[u][1]& -\infty

\end

\right )

\times

\left(

\begin

f[v][0]\\

f[v][1]

\end

\right )

=\left(

\begin

f[u][0]\\

f[u][1]

\end

\right )

\end

\](而且可以發現,此式可用在「更新」中:\((原) \times (新加入)=(新)\) )

根據此式遞推下去,樹根的 \(dp\) 值就是樹根所在的重鏈的 \(\left(

\begin

g[u][0]& g[u][0] \\

g[u][1]& -\infty

\end

\right )\) 矩陣乘積 再乘上 \(\left(

\begin

0\\0\end

\right )\)

用線段樹維護每個點的 \(\left(

\begin

g[u][0]& g[u][0] \\

g[u][1]& -\infty

\end

\right )\) 和區間矩陣積即可。

總體思路有了後,還剩兩個細節。

一是,如何預處理出 \(g[u][0/1]\)。

還記得前面說的 \((原) \times (新加入)=(新)\) 嘛?

最原始 \(g[u][0]=0,g[u][1]=val[u]\) 。\(dfs\)一遍,每個點的輕子的 \(f\) 值更新此點的 \(g\) 值;最後別忘了加上重子,求出此點的 \(f\) 值並傳到其父節點。

二是,具體如何修改。

首先修改 \(x\) 的矩陣(變了的是 \(g[x][1]\)),然後往上跳鏈,修改跳到的 重鏈頂端點的父節點 的矩陣。

在修改時還會發現,被修改的點可能不止乙個輕子。但由於每個輕子對該點答案的貢獻是獨立的(詳見轉移方程),只需記下該輕子在修改前後對父節點的貢獻,然後相減更新父節點的矩陣。

細節還是很多的。

看到乙個大佬的概括,覺得非常精闢:

**寫吐+調吐……更想不到的是寫個題解都如此煎熬……

#include#include#include#define inf 1000000000

using namespace std;

int read()

const int n = 100005;

int n,m,val[n];

struct nodepool[n*2],*h[n];

int cnt1;

void addedge(int u,int v)

struct mat

mat operator * (const mat &b) const

return c;

}}m0[n],mm[n*2];

int dfn[n],top[n],sz[n],son[n],tot,bot[n],re[n],fa[n];

void dfs1(int u)

}void dfs2(int u)

else bot[top[u]]=u;

for(node *p=h[u];p;p=p->nxt)

if(!dfn[v=p->v])

}int g0,g1;

void getm(int u)

if(v=son[u])

else

}int cnt,root,ch[n*2][2];

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

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

build(ch[x][0]=++cnt,l,mid);

build(ch[x][1]=++cnt,mid+1,r);

mm[x]=mm[ch[x][0]]*mm[ch[x][1]];

}void change(int x,int l,int r,int c,int y0,int y1)

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

if(c<=mid) change(ch[x][0],l,mid,c,y0,y1);

else change(ch[x][1],mid+1,r,c,y0,y1);

mm[x]=mm[ch[x][0]]*mm[ch[x][1]];

}mat sum(int x,int l,int r,int l,int r)

void jump(int x,int y)

}int main()

return 0;

}

洛谷P4719 模板 動態dp

大概就是一條鏈一條鏈的處理 鏈 在這裡指重鏈 對於每一條鏈,對於其上每乙個點,先算出它自身和所有輕兒子的貢獻,當做這一步中這個點的 權值 然後就變成序列上dp,直接用線段樹維護 線段樹版本o n log 2 1 include2 include3 include4 using namespace s...

洛谷P4719 模板 動態 DP

給定一棵 n 個點的樹,點帶點權。有 m 次操作,每次操作給定 x,y 表示修改點 x 的權值為 y 你需要在每次操作之後求出這棵樹的最大權獨立集的權值大小。調到心態 從前天晚上開始就剛這道題。最大權獨立子集即 選出若干個不相鄰的點使得他們的權值最大 摘自akioi的神仙 若沒有修改,這道題就是樹形...

luoguP4719 模板 動態 DP

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