當火星貓走過一條路之後,這條路就不能再走了從這句話我們可以想出來,如果走過的這條路是橋,那麼火星貓就會再也無法走回去。
那麼我們可以先求一遍邊雙並縮點。如果您不會求邊雙,您可以看這個雲剪貼簿。
求邊雙的同時我們可以標記該邊雙內是否有有泡芙的邊。
求出邊雙縮點後再建圖,由邊雙連通分量縮點的性質我們知道縮出來的一定是棵樹,而且樹上的邊都是橋。所以問題轉變為:
一棵樹上每個點有點權(點權即該邊雙內是否有泡芙),每個邊有邊權(邊權即該橋上是否有泡芙),問樹上 \(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 記錄