今天学次小生成树,主要是拓展昨天所学。
首先,次小生成树分严格与不严格两种,严格就是sum{val2} > sum{val1},不严格就是sum{val2} >= sum{val1}
一个结论:次小生成树一定是MST加一条边,这样一定有环,在这个环中减一条边。
加一条边,加哪条?这个可以枚举
减一条边,减哪条?贪心的思路,分为严格和不严格,减<或者<=加进来的这条边 的所有环上的边中 val最大的那条
加边枚举,很简单,下面考虑一下减边的时候。
令加的这条边的端点为u, v,那么,环的路径就是u -> lca(u, v) + v -> lca(u, v)
在这个环上一个个找也太蠢了,考虑用倍增优化,用两个数组维护最大值和次大值
Max1[i][j],表示节点i,到它的第2 ^ j个祖先路径上的,最大边权
相应的,Max2[i][j],表示节点i,到它的第2 ^ j个祖先路径上的,次大边权
可以证明,次大边权是一定比加进来的边的边权小的,自己思考。
那么这题就转换成一个普通的倍增LCA + 边跑边维护我们想要的最大边权即可。
其实就是两个倍增,复杂度可以理解为O(mlogm)
代码的实现还是比较困难的,理解了也得一会敲..敲起来真自闭
//My Conquest Is the Sea of Stars.
#pragma GCC diagnostic error "-std=c++11"
#include<set>
#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<ext/rope>
#include<iostream>
#include<algorithm>
#define endl "\n"
#define fi first
#define se second
#define gcd __gcd
#define pb push_back
#define mp make_pair
#define lowbit(x) x & (-x)
#define PII pair<int, int>
#define all(x) x.begin(), x.end()
#define rep(i, a, b) for(__typeof(b) i = a; i <= (b); i++)
#define Rep(i, a, b) for(__typeof(a) i = a; i >= (b); i--)
#define FAST ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int mod = (int)1e9 + 7;
const int maxn = (int)3e5 + 5;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
using __gnu_cxx::crope;
#define int ll
struct edge{
int from, to, val;
friend bool operator <(edge a, edge b){
return a.val < b.val;
}
}es[maxn];
vector<int> G[maxn];
bool vis[maxn];
int fa[maxn], dep[maxn], acst[maxn][22];
int Max1[maxn][22], Max2[maxn][22];
int lg[maxn];
int n, m;
int Find(int x) { return fa[x] == x ? x : fa[x] = Find(fa[x]); }
void uni(int x, int y) { fa[Find(x)] = Find(y); }
void init() {
//memset(dep, 0, sizeof(dep));
//memset(acst, 0, sizeof(acst));
//memset(Max1, 0, sizeof(Max1));
//memset(Max2, 0, sizeof(Max2));
//rep(i, 0, n) vis[i] = 0;
rep(i, 0, n) fa[i] = i;
//rep(i, 0, n) G[i].clear();
rep(i, 1, n){
lg[i] = lg[i-1] + ((1 << lg[i-1]) == i);
}
}
void dfs(int x){
int len = G[x].size();
rep(i, 0, len - 1){
int y = G[x][i];
int nxt = es[y].from == x ? es[y].to : es[y].from;
int val = es[y].val;
if(acst[x][0] == nxt) continue;
acst[nxt][0] = x, Max1[nxt][0] = val;
dep[nxt] = dep[x] + 1;
dfs(nxt);
}
}
void work(){
rep(j, 1, 19){
rep(i, 1, n){
acst[i][j] = acst[acst[i][j-1]][j-1];
Max1[i][j] = max(Max1[i][j-1], Max1[acst[i][j-1]][j-1]);
Max2[i][j] = max(Max2[i][j-1], Max2[acst[i][j-1]][j-1]);
if(Max1[i][j-1] != Max1[acst[i][j-1]][j-1]) Max2[i][j] = max(Max2[i][j], min(Max1[i][j-1], Max1[acst[i][j-1]][j-1]));
}
}
}
int MAX(int x, int a, int b, int val)
{
if(val > Max1[a][b]) return max(x, Max1[a][b]);
return max(x, Max2[a][b]);
}
int Get(int x, int y, int val){
if(dep[x] < dep[y]) swap(x, y);
int i, res = 0;
while(dep[x] > dep[y]) {
i = lg[dep[x]-dep[y]] - 1;
res = MAX(res, x, i, val);
x = acst[x][i];
}
if(x == y) return res;
i = 0;
while(acst[x][i] != acst[y][i]) i++;
for( ; i >= 0; i--){
if(acst[x][i] != acst[y][i]) {
res = MAX(MAX(res, x, i, val), y, i, val);
x = acst[x][i], y = acst[y][i];
}
}
return MAX(MAX(res, x, 0, val), y, 0, val);
}
int32_t main()
{
scanf("%lld %lld", &n, &m);
init();
rep(i, 1, m){
scanf("%lld %lld %lld", &es[i].from, &es[i].to, &es[i].val);
}
sort(es + 1, es + m + 1);
int res = 0, cnt = 0;
rep(i, 1, m){
if(cnt >= n) break;
int fu = Find(es[i].from), fv = Find(es[i].to);
if(fu != fv){
vis[i] = 1;
uni(fu, fv);
res += es[i].val;
cnt++;
G[es[i].from].pb(i);
G[es[i].to].pb(i);
}
}
dep[1] = 1;
dfs(1);
work();
int ans = INF, t;
rep(i, 1, m){
if(!vis[i]){
t = Get(es[i].from, es[i].to, es[i].val);
ans = min(ans, res + es[i].val - t);
}
}
printf("%lld\n", ans);
return 0;
}
本文介绍了次小生成树的概念及其实现方法,包括严格与非严格次小生成树的区别,通过添加一条边形成环再删除环中最大边的方式求解,并使用倍增算法优化查询过程。
270

被折叠的 条评论
为什么被折叠?



