重构一遍代码之后终于码出了次小生成树
以此(第一篇博客)纪念。
次小生成树有严格和非严格两种,这边先讲严格,非严格只需要将做严格次小生成树的特判去掉就可以了。
基础
kruskal求最小生成树;
(我用倍增LCA做法所以)最近公共祖先LCA版;
这些理解了便可以解决这模板题了;
具体做法
-
先用kruskal跑最小生成树,记录下树边。
-
再用dfs建树,建树的同时用 d p [ x ] [ i ] dp[x][i] dp[x][i] 记录下从x开始向上跳 2 j 2^j 2j时的最大边和次大边并保证最大值和次大值不相同(如果是非严格次小生成树只需记录下最大边)。
-
(以下自己yy出来的,如果不对请谅解) 因为kruskal保证联通的区块都是最小的,所以任意除去最小生成树上的任何一条边,得到的两个联通块都是最小生成树,所以可以枚举非最小生成树树边,用LCA找出该边两端点在树上的路径,并用dp数组维护出最大值和次大值,将枚举到的非最小生成树边与树上路径的最大边替换便可得到用当前边替换得到的最小生成树(如果是严格次小生成树要保证最大值或次大值与枚举到的边不同,非严格则不用);
-
枚举所有边,便可得到用所有非树边替换树边得到的最小生成树(kruskal的贪心),然后进行打擂边可求出次小生成树的值了。
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<climits>
#define LL long long
using namespace std;
const LL maxn=1e5+5;
const LL maxm=1e5*3+5;
struct line{
int aside,bside;
LL value;
}link[maxm];//link数组存储所有边;
int ltop,m;
int ufa[maxn];//并查集
LL MSTsum;
bool isMST[maxm];//记录一条边是否是最小生成树边
vector<int> edge[maxn];//用vector存储树;
int n;
int lfa[maxn][40],lg[maxn],depth[maxn];//lfa是LCA中的倍增数组,lg是预处理出的log2的值(减小常数),depth数组存储每一节点在最小生成树的深度。
struct maxlist{
LL Max,seMax;
}dp[maxn][40];//dp数组处理出树上路径的最大次大边。
LL ans;
void add(int x,int y,int z){
link[++ltop].aside=x;
link[ltop].bside=y;
link[ltop].value=z;
}//add加边。
int ask(int x){
if(ufa[x]==x)return x;
return ufa[x]=ask(ufa[x]);
}//并查集路径压缩。
void unite(int x,int y){
ufa[x]=y;
}//合并。
bool cmp(line a,line b){
return a.value<b.value;
}//sort比较函数。
void kruskal(){
int sum=0;
sort(link+1,link+1+m,cmp);
for(int i=1;i<=m&&sum<n-1;i++){
int xx=ask(link[i].aside);
int yy=ask(link[i].bside);
if(xx!=yy){
isMST[i]=1;
unite(xx,yy);
sum++;
MSTsum+=link[i].value;
}
}
}//kruskal求最小生成树。
void dfs(int x,int f){//dfs建树
lfa[x][0]=f;
depth[x]=depth[f]+1;
for(int i=1;i<=lg[depth[x]];i++){
lfa[x][i]=lfa[lfa[x][i-1]][i-1];
dp[x][i].Max=max(dp[lfa[x][i-1]][i-1].Max,dp[x][i-1].Max);
dp[x][i].seMax=max(dp[lfa[x][i-1]][i-1].seMax,dp[x][i-1].seMax);
if(dp[lfa[x][i-1]][i-1].Max!=dp[x][i-1].Max){//保证最大次大值不同。
dp[x][i].seMax=max(dp[x][i].seMax,min(dp[lfa[x][i-1]][i-1].Max,dp[x][i-1].Max));
}//用dp数组记录树上路径的最大次大值。
}
for(int i=0;i<edge[x].size();i++){
int p=edge[x][i];
int to=link[p].aside+link[p].bside-x;
if(to==f)continue;
dp[to][0].Max=link[p].value;//当前往上跳1步的最大边权就是自己。
dp[to][0].seMax=-1;
dfs(to,x);
}//继续深搜。
}
int lca(int x,int y){//求LCA。
if(depth[x]<depth[y])swap(x,y);
while(depth[x]>depth[y])x=lfa[x][lg[depth[x]-depth[y]]-1];
if(x==y)return x;
for(int k=lg[depth[x]];k>=0;k--){
if(lfa[x][k]!=lfa[y][k]){
x=lfa[x][k];
y=lfa[y][k];
}
}
return lfa[x][0];
}
void beat(LL &m1,LL &m2,LL beater){//求取最大次大值时打擂。
if(beater>m1){
m2=m1;
m1=beater;
return;
}
if(beater>m2&&beater!=m1){
m2=beater;
return;
}
return;
}
LL donate(int x,int y,int c){//倍增求取加入某非树边会最对边权和的贡献。
int fa=lca(x,y);
LL m1=-1,m2=-1;
while(depth[x]>depth[fa]){
beat(m1,m2,dp[x][lg[depth[x]-depth[fa]]-1].Max);
x=lfa[x][lg[depth[x]-depth[y]]-1];
}
while(depth[y]>depth[fa]){
beat(m1,m2,dp[y][lg[depth[y]-depth[fa]]-1].Max);
y=lfa[y][lg[depth[y]-depth[fa]]-1];
}
if(m1!=c){
return c-m1;
}
if(m2!=-1){
return c-m2;
}
return 0;
}
void init(){//读入。
int x,y,z;
scanf("%d%d",&n,&m);
memset(edge,0,sizeof(edge));
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=1;i<=n;i++){
ufa[i]=i;
}
for(int i=1;i<=n;i++){
lg[i]=lg[i-1]+((1<<lg[i-1])==i);
}
}
void work(){//主函数。
kruskal();
for(int i=1;i<=m;i++){
if(isMST[i]){
edge[link[i].aside].push_back(i);
edge[link[i].bside].push_back(i);
}
}
dfs(1,0);
ans=INT_MAX;
for(int i=1;i<=m;i++){
if(!isMST[i]){
LL t=donate(link[i].aside,link[i].bside,link[i].value);
if(t!=0)ans=min(ans,t);
}
}
}
void print(){//输出。
printf("%lld",MSTsum+ans);
}
int main(){
init();
work();
print();
}