一棵 \(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解決,本質都是將樹拆成鏈,這裡用樹剖。設 ...