洛谷P2607題解

想要深刻學習樹形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,gfr,to,不要手快打錯了。
相關文章
相關標籤/搜索