原文必点

原题链接

题目描述

给定一张\(N\) 个点$ M $条边的无向图,求无向图的严格次小生成树。

设最小生成树的边权之和为\(sum\),严格次小生成树就是指边权之和大于\(sum\)的生成树中最小的一个。

输入格式

第一行包含两个整数\(N\)和\(M\)。

接下来\(M\)行,每行包含三个整数\(x,y,z\),表示点\(x\)和点\(y\)之前存在一条边,边的权值为\(z\)。

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

数据范围

\[N \le 10^5 \\\\
M \le 3*10^5
\]

输入样例:

5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

输出样例:

11

解题报告

题意理解

要你构造一棵\(n\)个节点的严格次小生成树.

算法解析

分析条件

题目中给出的关键点,就是严格和次小.

  1. 什么是严格

就是题目强制要求严格单调性,不可以有\(=\)号的出现.

  1. 什么是次小

我们应该都知道,最小生成树,它要求边集合的边总和最小,那么次小生成树,要求边集合的边总和只比最小生成树边集合权值大.

总结性质

有至少一个(严格)次小生成树,和最小生成树之间只有一条边的差异。和真理只有一点差异,那就是出题人毒瘤

我们来粗略证明一下.(强行伪证)

我们知道最小生成树,是由\(n-1\)条构成的.

那么其他的\(M-N+1\)就是多余边.

假如说我们把一条多余边\((x,y,z)\),加入到了最小生成树中,那么一定会在\((x,y)\)之间的路径上形成一个环.

那么这个环上面,最大的边称之为

\[Val_1
\]

次大的边,称之为

\[Val_2
\]

而且为了保证严格这个单调性质,我们必须

\[Val_1>Val_2 \quad 最大的边一定大于次大的边
\]

接下来,我们就需要好好分析一下这条多余边了.

我们知道多余边,替换任何一条树上的一条边,都会使得最小生成树,不再最小

为什么?

因为最小生成树上的每一条边,一定是满足贪心性质下的最小的边.为什么啊?相信你的直觉啊

这个证明,我们使用的克鲁斯卡尔算法,已经告诉我们为什么.真相只有一个,我懒了

总而言之,言而总之,我们现在知道了这条多余边的加入.,一定会产生非最小生成树.

我们不妨令

\[ans=最小生成树边权之和
\]

假如说我们将多余边,替换掉最大权值边.

\[Val_1 ==> z \\\
此时我们发现当前生成树 W=ans+z-Val_1 \\\\
W=最小生成边权之和+加上多余边-最大权值边
\]

这一轮替换,我们可以认为这棵生成树有潜力成为次小生成树.

然后,我们发现,换一换次大边,也是可以的.

我们将多余边,强行替换掉次大权值边.

\[Val_2 ==> z \\\\
此时当前生成树 W=ans+z-Val_2 \\\\
W=最小生成树之和+加入多余边-次大权值边
\]

现在所有的候选生成树都出来了,但是我们面临一个非常严重的问题.

我们如何快速计算,一条路径上的最大边,和次大边.


动态规划

我们可以当前需要知道的状态,无非就是两个.

  1. 一条路径上的最大边
  2. 一条路径上的严格次大边

所以说,我们不妨就按照倍增数组的思路,去制造两个新数组.

  1. 最大边数组
  2. 严格次大边数组

\[f[x][k]=f[fa[x][k-1]][k-1]
\]

这是我们非常熟悉的Lca倍增数组.

然后咱们现在其实,手上掌握的最有力的性质,就是最值性质.

我们假设一条路径是由三段构造而成.

是三段,不是就三个点.

\[a=>c,c=>b,b=>a
\]

我们发现

\[A=>B的最大值其实等于 \\\\
max(A=>C最大值,B=>C最大值)
\]

这就是区间最值性质.

不过严格次大边,就比较麻烦了,不慌,咱们慢慢画图来.

为了下面简述方面,我们设置一下变量.

\[A=>C上最大边权为Val_{A,C} \quad 次大边权为V_{A,C} \\\\
C=>B上最大边权为Val_{B,C} \quad 次大边权为V_{B,C} \\\\
A=>B上最大边权为Val_{A,B} \quad 次大边权为V_{A,B} \\\\
\]

巧计一下,Val字母多,所以是最大边权,V字母少,所以是次大边权.

我们分类讨论一下,三种情况.

①第一段最大值=第二段最大值

\[Val_{A,C}=Val_{B,C}
\]

我们发现两段居然最大值一样.

次大边权就只能

\[V_{A,B}=max(V_{A,C},V_{B,C})
\]

②第一段最大值<第二段最大值.

那么此时,次大边权可以取第一段最大值.

因为此时总段的最大值,一定是第二段最大值.

\[Val_{A,B}=Val_{B,C} \\\\
因此V_{A,B}可以=Val_{A,C}
\]

综上所述,我们总结下来就是.

\[V_{A,B}=max(Val_{A,C},V_{B,C})
\]

③第一段最大值>第二段最大值.

那么此时,次大边权是可以取第二段最大值.

因为此时总段的最大值,一定是第一段最大值.

\[Val_{A,B}=Val_{A,C} \\\\
因此V_{A,B}可以=Val_{B,C}
\]

同样,总结一下.

\[V_{A,B}=max(Val_{B,C},v_{A,B})
\]

然后我们将\(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;
}

严格次小生成树[BJWC2010]的更多相关文章

  1. P4180 【模板】严格次小生成树[BJWC2010]

    P4180 [模板]严格次小生成树[BJWC2010] 倍增(LCA)+最小生成树 施工队挖断学校光缆导致断网1天(大雾) 考虑直接枚举不在最小生成树上的边.但是边权可能与最小生成树上的边相等,这样删 ...

  2. 【洛谷】4180:【模板】严格次小生成树[BJWC2010]【链剖】【线段树维护最大、严格次大值】

    P4180 [模板]严格次小生成树[BJWC2010] 题目描述 小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说, ...

  3. 「LuoguP4180」 【模板】严格次小生成树[BJWC2010](倍增 LCA Kruscal

    题目描述 小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得 ...

  4. Luogu P4180 【模板】严格次小生成树[BJWC2010]

    P4180 [模板]严格次小生成树[BJWC2010] 题意 题目描述 小\(C\)最近学了很多最小生成树的算法,\(Prim\)算法.\(Kurskal\)算法.消圈算法等等.正当小\(C\)洋洋得 ...

  5. P4180 严格次小生成树[BJWC2010] Kruskal,倍增

    题目链接\(Click\) \(Here\). 题意就是要求一个图的严格次小生成树.以前被题面吓到了没敢做,写了一下发现并不难. 既然要考虑次小我们就先考虑最小.可以感性理解到一定有一种次小生成树,可 ...

  6. 【【模板】严格次小生成树[BJWC2010]】

    树上的路径怎么能没有树剖 显然,次小生成树和最小生成树只在一条边上有差距,于是我们就可以枚举这一条边,将所有边加入最小生成树,之后再来从这些并不是那么小的生成树中找到那个最小的 我们往最小生成树里加入 ...

  7. 【luogu P4180 严格次小生成树[BJWC2010]】 模板

    题目链接:https://www.luogu.org/problemnew/show/P4180 这个题卡树剖.记得开O2. 这个题inf要到1e18. 定理:次小生成树和最小生成树差距只有在一条边上 ...

  8. 【洛谷 P4180】【模板】严格次小生成树[BJWC2010](倍增)

    题目链接 题意如题. 这题作为我们KS图论的T4,我直接打了个很暴力的暴力,骗了20分.. 当然,我们KS里的数据范围远不及这题. 这题我debug了整整一个晚上还没debug出来,第二天早上眼前一亮 ...

  9. 严格次小生成树[BJWC2010] (树链剖分,倍增,最小生成树)

    题目链接 Solution 有几点关键,首先,可以证明次小生成树一定是由最小生成树改变一条边而转化来. 所以需要枚举所有非最小生成树的边\((u,v)\).并且找到 \(u\) 到 \(v\) 的边中 ...

  10. 洛谷 P4180 【模板】严格次小生成树[BJWC2010]【次小生成树】

    严格次小生成树模板 算法流程: 先用克鲁斯卡尔求最小生成树,然后给这个最小生成树树剖一下,维护边权转点权,维护最大值和严格次大值. 然后枚举没有被选入最小生成树的边,在最小生成树上查一下这条边的两端点 ...

随机推荐

  1. C++ N叉树的实现

    引言 最近一个项目需要使用多叉树结构来存储数据,但是基于平时学习的都是二叉树的结构,以及网上都是二叉树为基础来进行学习,所以今天实现一个多叉树的数据结构. 理论基础 树和二叉树: 多叉树:多叉树,顾名 ...

  2. 单点登录之CAS原理和实现(转载)

    转载源:https://www.jianshu.com/p/613c615b7ef1 单点登录之CAS原理和实现 来源于作者刘欣的<码农翻身> + 自己的备注理解 这家集团公司财大气粗,竟 ...

  3. JavaScript 3种内置对象

    前面我们学了对象,如何创建对象及使用对象. 内置对象不需要实例化,就可以使用. 可以通俗地理解,在内存里的东东是对象,也就是实例化好的.在磁盘里的东东是类,需要实例化才能使用.实例化后的东东在内存里. ...

  4. Android性能专项分类

    性能专项分类:1.资源消耗2.启动耗时3.主要页面加载时间4.内存泄漏.抖动5.卡顿.页面渲染 一.资源消耗:CPU.内存.流量.功耗-----1.查看CPU占用率:adb shell dumpsys ...

  5. Linux 打包QT程序到未安装QT的其他Linux主机下运行

    昨天终于改好了一个开源但是用起来有问题的串口调试助手,想把它打包一下以后在其他电脑上也可以用. 找了网上的一个教程打包后,在本机上可以正常使用,但是移植到另一台上就出现缺少xcb的提示. 上网搜资料倒 ...

  6. PAT A1065 A+B and C (64bit) (20 分)

    AC代码 #include <cstdio> int main() { #ifdef ONLINE_JUDGE #else freopen("1.txt", " ...

  7. # G++出现cannot open output file … : Permission denied问题

    G++出现cannot open output file - : Permission denied问题 这是因为之前的编译运行程序没有退出,导致下一次编译运行无法进行,这应该是命令行下运行才可能出现 ...

  8. Java中的自动拆装箱(转)

    出处: 一文读懂什么是Java中的自动拆装箱 本文主要介绍Java中的自动拆箱与自动装箱的有关知识.  基本数据类型 基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型.它们是我 ...

  9. 怎样获取NodeList某位置上的节点

    1. 使用类似 Array 的中括号写法: document.body.childNodes[0] 2. 使用 NodeList.prototype.item(): document.body.chi ...

  10. lua与c的交互(函数专用)

    Lua与C的交互 Lua是一个嵌入式的语言,它不仅可以是一个独立运行的程序,也可以是一个用来嵌入其它应用的程序库. C API是一个C代码与Lua进行交互的函数集,它由以下几部分构成: 1.  读写L ...