1. 程式人生 > >[BZOJ1977] [洛谷4180] [BJWC2010] 嚴格次小生成樹 (kurskal+並查集+LCA)

[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%的資料N2000N≤2000,M3000M≤3000;80%的資料N50000N≤50000,M100000M≤100000;100%的資料N100000N≤100000,M300000M≤300000,邊權值非負且不超過10910^9

題意

求一個除最小生成樹之外的最小生成樹。。

題解

要求次小生成樹嘛,那我們就先求最小生成樹好了,就可以得到邊權和ans。 然後我們就可以求出整棵樹的權值之和,我們稱最小生成樹上的邊為“樹邊”,其他多出來的邊為“非樹邊”。 接下來我們考慮一下,次小生成樹嘛,那不就是用一條“非樹邊”替換掉其中的一條“樹邊”所形成的生成樹最小嗎? 注意哦:我們要求的是嚴格次小生成樹,要嚴格哦,嚴格是什麼意思呢,就是邊權和一定小於最小生成樹,而不能等於。

我們把一條非樹邊(x,y,z)(x,y,z)新增到最小生成樹中,就會和樹上原本的(x,y)(x,y)之間的樹邊構成一個環,然後我們設x,yx,y之間的樹邊的最大值為mx1mx1,嚴格次大邊為mx2(mx1>mx2)mx2。(mx1>mx2) 如果z>mx1z>mx1,就把mx1mx1對應的那條邊替換成(x,y,z)(x,y,z)這條邊,就得到了次小生成樹的一個候選結果,邊權和就是ansmx1+zans-mx1+z

。 如果z=mx1z=mx1,就把mx2mx2對應的那條邊替換成(x,y,z)(x,y,z)這條邊,也得到了次小生成樹的一個候選結果,邊權和就是ansmx2+zans-mx2+z。

我們列舉每一條邊,與最小生成樹上的邊替換,計算出上述的所有“候選結果”。在所有候選結果中,選擇一個最小的就是整個圖的嚴格次小生成樹。 這樣問題就來了:我們要如何快速地求出一條路徑上的最大邊權和次大邊權呢?

答案:樹上倍增!!! 設f[x][k]f[x][k]表示的是x的第2k2^k輩祖先,d1[x][k]d1[x][k]d2[x][k]d2[x][k]分別表示從xxf[x][k]f[x][k]的路徑上的最大邊權和嚴格次大邊權。 於是對於任意k[1,logn]k∈[1,\log n]就有(自己拿張紙推一下): 1、f[x][k]=f[f[x][k1]][k1]f[x][k]=f[f[x][k-1]][k-1](畫個圖感受一下); 2、d1[x][k]=max{d1[x][k1],d1[f[x][k1]][k1]}d1[x][k]=max\{d1[x][k-1],d1[f[x][k-1]][k-1]\} 3、d2[x][k]={max{d2[x][k1],d2[f[x][k1]][k1]}(d1[x][k1]=d1[f[x][k1]][k1])max{d1[x][k1],d2[f[x][k1]][k1]}(d1[x][k1]<d1[f[x][k1]][k1])max{d2[x][k1],d1[f[x][k1]][k1]}(d1[x][k1]>d1[f[x][k1]][k1]) d2[x][k]= \left \{\begin{array}{cc} max\{d2[x][k-1],d2[f[x][k-1]][k-1]\}(d1[x][k-1]=d1[f[x][k-1]][k-1])\\ max\{d1[x][k-1],d2[f[x][k-1]][k-1]\}(d1[x][k-1]<d1[f[x][k-1]][k-1])\\ max\{d2[x][k-1],d1[f[x][k-1]][k-1]\}(d1[x][k-1]>d1[f[x][k-1]][k-1]) \end{array}\right. k=0k=0時: 1、f[x][0]=father(x)f[x][0]=father(x)(這裡的father(x)father(x)並非fa[x]fa[x]) 2、d1[x][0]=edge(x,father(x))d1[x][0]=edge(x,father(x)) 3、d2[x][0]=d2[x][0]=-\infty

下來就考慮每條非樹邊(x,y,z)(x,y,z)。採用倍增演算法求LCA的框架,x,yx,y每向上移動一段路徑,就把每段路徑對應的最大邊權和嚴格次大邊權按照與求d1d1d2d2陣列類似的方法合併到答案中,最後就可以得到(x,y)(x,y)之間的路徑上的最大邊權和嚴格次大邊權。

好啦,以上都是廢話,我知道你們只看這個。

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