題目描述
給定一張\(N\) 個點$ M $條邊的無向圖,求無向圖的嚴格次小生成樹。
設最小生成樹的邊權之和為\(sum\),嚴格次小生成樹就是指邊權之和大於\(sum\)的生成樹中最小的一個。
輸入格式
第一行包含兩個整數\(N\)和\(M\)。
接下來\(M\)行,每行包含三個整數\(x,y,z\),表示點\(x\)和點\(y\)之前存在一條邊,邊的權值為\(z\)。
輸出格式
包含一行,僅一個數,表示嚴格次小生成樹的邊權和。(數據保證必定存在嚴格次小生成樹)
數據范圍
輸入樣例:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
輸出樣例:
11
解題報告
題意理解
要你構造一棵\(n\)個節點的嚴格次小生成樹.
算法解析
分析條件
題目中給出的關鍵點,就是嚴格和次小.
- 什么是嚴格
就是題目強制要求嚴格單調性,不可以有\(=\)號的出現.
- 什么是次小
我們應該都知道,最小生成樹,它要求邊集合的邊總和最小,那么次小生成樹,要求邊集合的邊總和只比最小生成樹邊集合權值大.
總結性質
有至少一個(嚴格)次小生成樹,和最小生成樹之間只有一條邊的差異。和真理只有一點差異,那就是出題人毒瘤
我們來粗略證明一下.(強行偽證)
我們知道最小生成樹,是由\(n-1\)條構成的.
那么其他的\(M-N+1\)就是多余邊.
假如說我們把一條多余邊\((x,y,z)\),加入到了最小生成樹中,那么一定會在\((x,y)\)之間的路徑上形成一個環.
那么這個環上面,最大的邊稱之為
次大的邊,稱之為
而且為了保證嚴格這個單調性質,我們必須設
接下來,我們就需要好好分析一下這條多余邊了.
我們知道多余邊,替換任何一條樹上的一條邊,都會使得最小生成樹,不再最小
為什么?
因為最小生成樹上的每一條邊,一定是滿足貪心性質下的最小的邊.為什么啊?相信你的直覺啊
這個證明,我們使用的克魯斯卡爾算法,已經告訴我們為什么.真相只有一個,我懶了
總而言之,言而總之,我們現在知道了這條多余邊的加入.,一定會產生非最小生成樹.
我們不妨令
假如說我們將多余邊,替換掉最大權值邊.
這一輪替換,我們可以認為這棵生成樹有潛力成為次小生成樹.
然后,我們發現,換一換次大邊,也是可以的.
我們將多余邊,強行替換掉次大權值邊.
現在所有的候選生成樹都出來了,但是我們面臨一個非常嚴重的問題.
我們如何快速計算,一條路徑上的最大邊,和次大邊.
動態規划
我們可以當前需要知道的狀態,無非就是兩個.
- 一條路徑上的最大邊
- 一條路徑上的嚴格次大邊
所以說,我們不妨就按照倍增數組的思路,去制造兩個新數組.
- 最大邊數組
- 嚴格次大邊數組
這是我們非常熟悉的Lca倍增數組.
然后咱們現在其實,手上掌握的最有力的性質,就是最值性質.
我們假設一條路徑是由三段構造而成.
是三段,不是就三個點.
我們發現
這就是區間最值性質.
不過嚴格次大邊,就比較麻煩了,不慌,咱們慢慢畫圖來.
為了下面簡述方面,我們設置一下變量.
巧計一下,Val字母多,所以是最大邊權,V字母少,所以是次大邊權.
我們分類討論一下,三種情況.
①第一段最大值=第二段最大值
我們發現兩段居然最大值一樣.
次大邊權就只能
②第一段最大值<第二段最大值.
那么此時,次大邊權是可以取第一段最大值.
因為此時總段的最大值,一定是第二段最大值.
綜上所述,我們總結下來就是.
③第一段最大值>第二段最大值.
那么此時,次大邊權是可以取第二段最大值.
因為此時總段的最大值,一定是第一段最大值.
同樣,總結一下.
然后我們將\(A,B,C\)具體化一下.
A其實就是起始節點.
C其實就是A跳躍了\(2^{i-1}\)格節點.
B其實就是A跳躍了\(2^{i}\)格節點.
廣告時間:發現還是有點模糊,咱們的直播課會講解的非常清晰,畫圖肯定少不了.
代碼解析
#include <bits/stdc++.h>
using namespace std;
#define INF 1e16
const int N=1e5+200;
const int M=6*1e5+300;
int head[M],edge[M],Next[M],ver[M],tot,fa[M],n,m,father[N][32],deep[N];
long long dp[2][N][32],val1,val2,ans_max,ans;
struct node
{
int x,y,z,vis;
} s[M];
int cmp(node a,node b)
{
return a.z<b.z;
}
struct Edge
{
void init2()
{
memset(head,0,sizeof(head));
tot=0;
}
void add_edge(int a,int b,int c)
{
edge[++tot]=b;
ver[tot]=c;
Next[tot]=head[a];
head[a]=tot;
}
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void Kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1; i<=m; i++)
{
int a=find(s[i].x),b=find(s[i].y);
if (a==b)
continue;
s[i].vis=1;
fa[a]=b;
ans+=s[i].z;
add_edge(s[i].x,s[i].y,s[i].z);
add_edge(s[i].y,s[i].x,s[i].z);
}
}
void bfs(int root)
{
deep[root]=0;
queue<int> q;
q.push(root);
while(q.size())
{
int x=q.front(),len=(int)log2(deep[x]+1);
q.pop();
for(int i=head[x]; i; i=Next[i])
{
int y=edge[i];
if(y==father[x][0])
continue;
deep[y]=deep[x]+1;
father[y][0]=x,dp[0][y][0]=ver[i],dp[1][y][0]=-INF;
q.push(y);
for(int t=1; t<=len; t++)
{
father[y][t]=father[father[y][t-1]][t-1];
if(dp[0][y][t-1]!=dp[0][father[y][t-1]][t-1])
{
dp[0][y][t]=max(dp[0][y][t-1],dp[0][father[y][t-1]][t-1]);
dp[1][y][t]=min(dp[0][y][t-1],dp[0][father[y][t-1]][t-1]);
}
else
{
dp[0][y][t]=dp[0][y][t-1];
dp[1][y][t]=max(dp[1][y][t-1],dp[1][father[y][t-1]][t-1]);
}
}
}
}
}
inline void update2(int x)
{
if(x>val1)
val2=val1,val1=x;
else if(x>val2 && x!=val1)
val2=x;
}
inline void update(int x, int t)
{
update2(dp[0][x][t]);
update2(dp[1][x][t]);
}
inline void Lca(int x, int y)
{
val1=val2=-INF;
if(deep[x]<deep[y])
swap(x,y);
while(deep[x]>deep[y])
{
int t=(int)log2(deep[x]-deep[y]);
update(x,t),x=father[x][t];
}
if(x==y)
return;
for(int t=(int)log2(deep[x]); t>=0; t--)
{
if(father[x][t]!=father[y][t])
{
update(x,t),update(y,t);
x=father[x][t];
y=father[y][t];
}
}
update(x,0),update(y,0);
}
} g1;
int main()
{
// freopen("stdin.in","r",stdin);
// freopen("stdout.out","w",stdout);
scanf("%d%d",&n,&m);
g1.init2();
for(int i=1; i<=m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
s[i].x=a,s[i].y=b,s[i].z=c;
fa[i]=i;
}
g1.Kruskal();
g1.bfs(1);
ans_max=INF;
for(int i=1; i<=m; i++)
{
if(!s[i].vis)
{
g1.Lca(s[i].x,s[i].y);
if(val1!=s[i].z)
ans_max=min(ans_max,ans-val1+s[i].z);
else
ans_max=min(ans_max,ans-val2+s[i].z);
}
}
printf("%lld\n",ans_max);
return 0;
}