P4180 【模板】严格次小生成树[BJWC2010]
P4180 【模板】严格次小生成树[BJWC2010]
题目描述
小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值) \(\sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e)\)
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。
输入输出格式
输入格式:
第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。
输出格式:
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
首先明确严格, 指的是次小生成树边权之和小于而不是小于等于最小生成树
搞清楚这点, 我们就有了一个大概思路: 先构造一个最小生成树, 枚举每一条没选上的边, 查询这条边连接的两点之间在最小生成树上的最大值和次大值(次大值防止出现等于的情况, 严格), 进行替换, 维护答案最小即可。
首先想到树剖, 发现线段树维护次大值较为繁琐且查询复杂度不够理想, 常熟较大。
考虑倍增 \(LCA\) ,在倍增初始化时预处理出一段链上(具体是从当前点向上跳 \(2^{i}\) 个点的这条链上)的最大、次大值。因为树上两点间有且仅有一条路径且此路径通过他们的 \(LCA\) ,故查询最大、 次大值时, 分别倍增查询两个节点到其 \(LCA\) 的最大值即可
查询过程中注意严格这一定义
复杂度:\(O(Kruskal) + O(n\log n)\)
Code
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
typedef long long LL;
using namespace std;
LL RD(){
LL out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const LL maxn = 300019,INF = 0xfffffffffffffff;
LL head[maxn],nume = 1;
struct Node{
LL v,dis,nxt;
}E[maxn << 3];
void add(LL u,LL v,LL dis){
E[++nume].nxt = head[u];
E[nume].v = v;
E[nume].dis = dis;
head[u] = nume;
}
LL num, nr;
LL father[maxn];
LL findfather(LL v){
if(father[v] == v)return v;
return father[v] = findfather(father[v]);
}
struct Edge{
LL u, v, dis;
bool used;
}I[maxn];
bool cmp(Edge a, Edge b){return a.dis < b.dis;}
LL tot, root;
LL jump[maxn][25],dep[maxn];
LL maxx[maxn][25], mx[maxn][25];
void dfs(LL u, LL F){
for(LL i = head[u];i;i = E[i].nxt){
LL v = E[i].v;
if(v == F)continue;
jump[v][0] = u;
dep[v] = dep[u] + 1;
maxx[v][0] = E[i].dis;
mx[v][0] = -INF;
dfs(v, u);
}
}
void get_jump(LL s){
dep[s] = 1;
dfs(s, -1);
for(LL i = 1;i <= 19;i++){
for(LL j = 1;j <= num;j++){
LL F = jump[j][i - 1];
jump[j][i] = jump[F][i - 1];
maxx[j][i] = max(maxx[j][i - 1], maxx[F][i - 1]);
mx[j][i] = max(mx[j][i - 1], mx[F][i - 1]);
if(maxx[j][i] != maxx[j][i - 1])mx[j][i] = max(mx[j][i], maxx[j][i - 1]);
else mx[j][i] = max(mx[j][i], maxx[F][i - 1]);
}
}
}
LL LCA(LL x, LL y){
if(dep[x] < dep[y])swap(x, y);
for(LL i = 19;i >= 0;i--)if(dep[jump[x][i]] >= dep[y])x = jump[x][i];
if(x == y)return x;
for(LL i = 19;i >= 0;i--)if(jump[x][i] != jump[y][i])x = jump[x][i], y = jump[y][i];
return jump[x][0];
}
LL get_max(LL x, LL y, LL MAX){
LL lca = LCA(x, y);
LL ans = -INF;
for(LL i = 19;i >= 0;i--){
if(dep[jump[x][i]] >= dep[lca]){
if(maxx[x][i] < MAX)ans = max(ans, maxx[x][i]);
else ans = max(ans, mx[x][i]);
x = jump[x][i];
}
}
for(LL i = 19;i >= 0;i--){
if(dep[jump[y][i]] >= dep[lca]){
if(maxx[y][i] < MAX)ans = max(ans, maxx[y][i]);
else ans = max(ans, mx[y][i]);
y = jump[y][i];
}
}
return ans;
}
int main(){
num = RD();nr = RD();
for(LL i = 1;i <= num;i++)father[i] = i;
for(LL i = 1;i <= nr;i++)I[i].u = RD(), I[i].v = RD(), I[i].dis = RD();
sort(I + 1, I + 1 + nr, cmp);
for(LL i = 1;i <= nr;i++){
LL faA = findfather(I[i].u), faB = findfather(I[i].v);
if(faA != faB){
root = I[i].u;
father[faA] = faB, tot += I[i].dis;
add(I[i].u, I[i].v, I[i].dis);
add(I[i].v, I[i].u, I[i].dis);
I[i].used = 1;
}
}
get_jump(root);
LL ans = INF;
for(LL i = 1;i <= nr;i++){
if(!I[i].used){
LL now = get_max(I[i].u, I[i].v, I[i].dis);
ans = min(ans, tot - now + I[i].dis);
}
}
printf("%lld\n", ans);
return 0;
}