P2416 泡芙 題解

2022-09-19 18:57:09 字數 1965 閱讀 2670

當火星貓走過一條路之後,這條路就不能再走了

從這句話我們可以想出來,如果走過的這條路是橋,那麼火星貓就會再也無法走回去。

那麼我們可以先求一遍邊雙並縮點。如果您不會求邊雙,您可以看這個雲剪貼簿。

求邊雙的同時我們可以標記該邊雙內是否有有泡芙的邊。

求出邊雙縮點後再建圖,由邊雙連通分量縮點的性質我們知道縮出來的一定是棵樹,而且樹上的邊都是橋。所以問題轉變為:

一棵樹上每個點有點權(點權即該邊雙內是否有泡芙),每個邊有邊權(邊權即該橋上是否有泡芙),問樹上 \(u\to v\) 的一條路徑上點權和或邊權和是否大於等於 \(1\)。

我們可以從根向下進行字首和。每個點都有兩個值要儲存:乙個是從根到這個點的路徑的點權和,乙個是從根到這裡的路徑的邊權和。

我們再求出 \((u,v)\) 的 lca。如果您不會 lca 請移步p3379 最近公共祖先。

接下來對於 \(u\to v\) 路徑的點權和和邊權和分別推式子:

最後只需要檢查邊權和和點權和的值即可。

您需要知道我的巨集定義都是什麼意思,您可以在這裡檢視我的預設源。

這裡省略了求橋部分,您可以移步p1656 炸鐵路的 tarjan 做法學習如何求橋。

dfs 不走橋邊法求邊雙:

void dfs(int x)

ctn;//避免無限遞迴

} if(bridge[i])

if(edge[i].val==1)

dfs(y);

}}

\(has\) 陣列所表示的是該邊雙中是否有泡芙,\(tnt\) 是當前邊雙連通分量的編號。

主函式中呼叫它的方式如下:

fr1(i,1,n)

}

只有這樣才能保證每個點都有了對應的邊雙。

這裡只需要注意鏈式前向星的細節問題就行了:

fr1(i,2,edgecnt-1)

}

另附上我的鏈式前向星函式方便理解上面的**:

int edgecnt=2;

struct edge edge[n+n];

int head[n+n];

void add(int x,int y,int w)

從這裡開始,我們預設 \(1\) 為樹根,將這棵樹變為有根樹。

注意,字首和點權前要將pointvalue[1]=has[1],即要將樹根處的點權字首和初始化為樹根邊雙是否有泡芙。

接下來就可以從樹根開始往下 dfs,同時做邊權字首和與點權字首和。此外,我們可以順便完成 lca 的初始化。

void dfs2(int x,int fa)

}}

\(psum\) 是點權字首和,\(tsum\) 是邊權字首和。主函式中應該如下呼叫:

psum[1]=has[1];//不可缺失的初始化!!!

dfs2(1,1);

倍增 lca 的部分您可以移步p3379 最近公共祖先。

注意 lca 的遞推部分,不要把迴圈寫反。只要心中牢記 \(f\) 陣列的含義,就永遠不會發生問題。

num=log2(n);

fr1(j,1,num)

}

求 lca 的部分不給出參考**。

cin>>q;

while(q--)

else

}

注意輸入的並不是邊雙的編號,我們要自己轉化一下。

求出 lca 後就可以套用思路裡面推出的公式了。注意我們在計算點權貢獻時要判斷 \(lcaa\) 是否是 \(1\),如果是的話就不能減去psum[f[lcaa][0]](因為 lca 預處理時我們使用了f[1][0]=1),而是要減去 \(0\)。

ac 記錄