[BZOJ1977] [洛谷4180] [BJWC2010] 嚴格次小生成樹 (kurskal+並查集+LCA)
題目
Description
小 C 最近學了很多最小生成樹的演算法,Prim 演算法、Kurskal 演算法、消圈演算法等等。 正當小 C 洋洋得意之時,小 P 又來潑小 C 冷水了。小 P 說,讓小 C 求出一個無向圖的次小生成樹,而且這個次小生成樹還得是嚴格次小的,也就是說: 如果最小生成樹選擇的邊集是 EM,嚴格次小生成樹選擇的邊集是 ES,那麼需要滿足:(value(e) 表示邊 e的權值) 這下小 C 蒙了,他找到了你,希望你幫他解決這個問題。
Input
第一行包含兩個整數N 和M,表示無向圖的點數與邊數。 接下來 M行,每行 3個數x y z 表示,點 x 和點y之間有一條邊,邊的權值為z。
Output
包含一行,僅一個數,表示嚴格次小生成樹的邊權和。(資料保證必定存在嚴格次小生成樹)
Sample Input
5 6 1 2 1 1 3 2 2 4 3 3 5 4 3 4 3 4 5 6
Sample Output
11
HINT
資料中無向圖無自環:50%的資料,;80%的資料,;100%的資料,,邊權值非負且不超過。
題意
求一個除最小生成樹之外的最小生成樹。。
題解
要求次小生成樹嘛,那我們就先求最小生成樹好了,就可以得到邊權和ans。 然後我們就可以求出整棵樹的權值之和,我們稱最小生成樹上的邊為“樹邊”,其他多出來的邊為“非樹邊”。 接下來我們考慮一下,次小生成樹嘛,那不就是用一條“非樹邊”替換掉其中的一條“樹邊”所形成的生成樹最小嗎? 注意哦:我們要求的是嚴格次小生成樹,要嚴格哦,嚴格是什麼意思呢,就是邊權和一定小於最小生成樹,而不能等於。
我們把一條非樹邊新增到最小生成樹中,就會和樹上原本的之間的樹邊構成一個環,然後我們設之間的樹邊的最大值為,嚴格次大邊為 如果,就把對應的那條邊替換成這條邊,就得到了次小生成樹的一個候選結果,邊權和就是。 如果,就把對應的那條邊替換成這條邊,也得到了次小生成樹的一個候選結果,邊權和就是
我們列舉每一條邊,與最小生成樹上的邊替換,計算出上述的所有“候選結果”。在所有候選結果中,選擇一個最小的就是整個圖的嚴格次小生成樹。 這樣問題就來了:我們要如何快速地求出一條路徑上的最大邊權和次大邊權呢?
答案:樹上倍增!!! 設表示的是x的第輩祖先,和分別表示從到的路徑上的最大邊權和嚴格次大邊權。 於是對於任意就有(自己拿張紙推一下): 1、(畫個圖感受一下); 2、 3、 當時: 1、(這裡的並非) 2、 3、
下來就考慮每條非樹邊。採用倍增演算法求LCA的框架,每向上移動一段路徑,就把每段路徑對應的最大邊權和嚴格次大邊權按照與求和陣列類似的方法合併到答案中,最後就可以得到之間的路徑上的最大邊權和嚴格次大邊權。
好啦,以上都是廢話,我知道你們只看這個。
Code
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5+10,MAXM=3e5+10;
struct E{int x,y,z,next;}ed[MAXM*2]; int last[MAXN],tot;
struct UE{int x,y,z; bool bk;}a[MAXM];
int fa[MAXN],dep[MAXN],f[MAXN][25];
ll d1[MAXN][25],d2[MAXN][25];//d1[x][k]代表的是從x到x的2^k輩祖先的路徑最大值,d2則是嚴格次大值。
ll mn=0x3f3f3f3f;// mn指的是所要替換的邊和原邊的差值,我們要求的是差值最小的,這樣就可以保證一定是次小生成樹
void ins(int x,int y,int z) {ed[++tot]=(E){x,y,z,last[x]}; last[x]=tot;}// 建邊。。
int get_fa(int x)// 找祖宗。。(並查集)
{
if(x==fa[x]) return x; // 如果自己的祖宗是自己的話,就返回
return fa[x]=get_fa(fa[x]);// 如果自己還不是的話,就繼續往下找
}
void dfs(int x,int fa)//預處理。。
{
for(int i=1;i<=20;i++)
{
if(dep[x]<(1<<i)) break; //如果層數小於2^i,就不能往上跳,不然就跳出去了
f[x][i]=f[f[x][i-1]][i-1];// x的第2^i輩祖先就是x的第2^(i-1)輩祖先的第2^(i-1)輩祖先(自己可以畫個圖感受一下)
// 下面就是根據我們上面推出來的公式執行。。
d1[x][i]=max(d1[x][i-1],d1[f[x][i-1]][i-1]);
if(d1[x][i-1]==d1[f[x][i-1]][i-1]) d2[x][i]=max(d2[x][i-1],d2[f[x][i-1]][i-1]);
else
{
d2[x][i]=min(d1[x][i-1],d1[f[x][i-1]][i-1]);
d2[x][i]=max(d2[x][i-1],d2[x][i]);
d2[x][i]=max(d2[x][i],d2[f[x][i-1]][i-1]);
}
}
// 下面就和普通的LCA的預處理一樣
for(int k=last[x];k;k=ed[k].next)
{
int y=ed[k].y;
if(y==fa) continue;// 如果y==fa,就表明這是一條重邊,我們不考慮。
f[y][0]=x;// y的第2^0(也就是爸爸)是x
d1[y][0]=ed[k].z;//剛開始時只有一條邊,就是ed[k],所以最大值就是自己
dep[y]=dep[x]+1;//y比x深一層
dfs(y,x);//繼續遞迴。。
}
}
int lca(int x,int y) // LCA
{
if(dep[x]<dep[y]) swap(x,y);// 保證x的層數在y下面
for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
// 如果x的第2^i個爸爸的層數還在y的層數下面或者和y的層數相等,就可以往上跳
// 當迴圈結束時,條件dep[f[x][i]]>=dep[y]即可保證x和y同層。
if(x==y) return x;// 當x與y重合時,就證明找到公共祖先了,公共祖先就是y,也是現在的x
for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];//x已經和y同層了,兩個同時向上跳相同的層數直到相等
// 當迴圈結束時,條件f[x][i]!=f[y][i]即可保證f[x][0]=f[y][0],所以再向上跳一層就是最近公共祖先(f[x][0])。
return f[x][0];
}
void cal(int x,int fa,int z)
{
ll