想要深刻学习树形DP,请点击个人博客。html
本题的DP模型同 P1352 没有上司的舞会。本题的难点在于如何把基环树DP转化为普通的树上DP。数组
考虑断边和换根。先找到其中的一个环,在上面随意取两个点, 断开这两个点的边,使其变为一棵普通树。以其中的一点为树根作树形DP,再以另外一点为树根再作一次树形DP,由于相邻的两点不能同时选,因此最后统计一下 \(f(i)(0)\) 与 \(g(j)(0)\) 的最大值便可。学习
定义 \(f(i)(0/1)\) 为第一次树形DP的 \(i\) 点的最优解,\(g(i)(0/1)\) 为第二次树形DP的 \(i\) 点的最优解。$\text{Ans} $ 为一次基环树DP的答案。\(\text{E}_\text{Circle}\) 为基环树的环上的点的集合。spa
故一次基环树DP的答案为:
\[ \text{Ans}=\max\{f(i)(0),g(j)(0)\} \]code
\[ (i,j\in \text{E}_\text{Circle},i\neq j) \]htm
下图为洛谷秋令营的课件讲解:blog
关键代码以下:ci
void covertree(int fr)//寻找基环树 { used[fr]=1; for(int i=head[fr];i;i=e[i].next) { int to=e[i].to; if(used[to]==0) { covertree(to); } } } void findcir(int fr,int fa)//寻找基环树中的环 { if(flag) return ; vis[fr]=1; for(int i=head[fr];i;i=e[i].next) { int to=e[i].to; if(vis[to]==0) { findcir(to,fr); }else if(to!=fa) { fri=fr;//第一个点 toi=to;//第二个点 E=i;//边的编号 flag=1; return ; } } } void DPf(int fr)//以其中的一点为树根作树形DP { visf[fr]=1; f[fr][1]=crit[fr]; for(int i=head[fr];i;i=e[i].next) { int to=e[i].to; if(visf[to]==0&&(i^1)!=E)//保证不会选到第一个点和第二个点,至关于断边 { DPf(to); f[fr][0]+=max(f[to][0],f[to][1]); f[fr][1]+=f[to][0]; } } } void DPg(int fr)//再以另外一点为树根再作一次树形DP { visg[fr]=1; g[fr][1]=crit[fr]; for(int i=head[fr];i;i=e[i].next) { int to=e[i].to; if(visg[to]==0&&(i^1)!=E) { DPg(to); g[fr][0]+=max(g[to][0],g[to][1]); g[fr][1]+=g[to][0]; } } } for(int i=1;i<=n;i++)//调用+统计答案 { if(used[i]==1) continue; covertree(i); flag=0; findcir(i,-1); DPf(fri); DPg(toi); ans+=max(f[fri][0],g[toi][0]); }
特别注意:element
本题是基环树森林,而不是单棵基环树,故要反复寻找覆盖基环树,最后将全部答案加起来。get
由于要断边,因此前向星计数器 ei
必定要初始化为 1。
used[]
,vis[]
,visf[]
,visg[]
)。f
,g
和 fr
,to
,不要手快打错了。