倍增lca
O(mlogn)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 40010, M = 16;
int n, m;
vector<int> g[N];
int root;
int depth[N];
int f[N][M];
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1;//记得初始化0点为0
queue<int> q;
q.push(root);
while(q.size())
{
int ver = q.front();
q.pop();
for(auto j : g[ver])
{
if(depth[j] > depth[ver] + 1)
{
depth[j] = depth[ver] + 1;
q.push(j);
f[j][0] = ver;
for(int k = 1; k < M; k ++)
{
f[j][k] = f[f[j][k - 1]][k - 1];
}
}
}
}
}
int lca(int a, int b)
{
if(depth[a] < depth[b])swap(a, b);
for(int k = M - 1; k >= 0; k --)
{
if(depth[f[a][k]] >= depth[b])
a = f[a][k];
}
if(a == b)return a;
for(int k = M - 1; k >= 0; k --)
{
if(f[a][k] != f[b][k])
{
a = f[a][k];
b = f[b][k];
}
}
return f[a][0];
}
int main()
{
IOS
cin >> n;
for(int i = 0; i < n; i ++)
{
int a, b;
cin >> a >> b;
if(b == -1)root = a;
else
{
g[a].push_back(b);
g[b].push_back(a);
}
}
bfs();
cin >> m;
while(m --)
{
int a, b;
cin >> a >> b;
int ver = lca(a, b);
if(ver == a)cout << 1 << endl;
else if(ver == b) cout << 2 << endl;
else cout << 0 << endl;
}
return 0;
}
离线lca(tarjan)
O(n+m)
理论时间复杂度比在线做法低,但实战不一定比倍增lca快。
向上标记法的优化
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 10010, M = 20010;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int res[M];
int dist[N];
int p[N];
int st[N];
vector<PII> query[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u, int fa)
{
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa)continue;
dist[j] = dist[u] + w[i];
dfs(j, u);
}
}
int find(int x)
{
if(p[x] != x)return p[x] = find(p[x]);
return x;
}
void tarjan(int u)
{
st[u] = 1;//这个点正在搜
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(!st[j])//没搜过的点
{
tarjan(j);
p[j] = u;
}
}
for(auto x : query[u])
{
int ver = x.first, id = x.second;
if(st[ver] == 2)
{
int anc = find(ver);
res[id] = dist[u] + dist[ver] - 2 * dist[anc];
}
}
st[u] = 2;//这个点搜完了
}
int main()
{
IOS
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
for(int i = 0; i < m; i ++)
{
int a, b;
cin >> a >> b;
if(a != b)
{
query[a].push_back({b, i});
query[b].push_back({a, i});
}
}
for(int i = 1; i <= n; i ++)p[i] = i;
tarjan(1);
for(int i = 0; i < m; i ++)cout << res[i] << endl;
return 0;
}
lca求严格次小生成树
所以要遍历每一条不在最小生成树里的边,加进去后再删去新生成环的除这条边外的最大值(还要<这条边) (因为新加的边一定>=这个环里的每条边,不然这个生成树就不是最小生成树了)
求不严格次小生成树的话直接 删去新生成环的除这条边外的最大值 即可,不用严格小于
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
struct Node
{
int a, b, w;
bool used;
bool operator< (const Node &t) const
{
return w < t.w;
}
} edges[N * 3];
int h[N], e[M], w[M], ne[M], idx;
int p[N];
int depth[N], fa[N][17], d1[N][17], d2[N][17];
ll sum;
int find(int x)
{
if(p[x] != x)return p[x] = find(p[x]);
return x;
}
void kruskal()
{
for(int i = 0; i < m; i ++)
{
int a = find(edges[i].a), b = find(edges[i].b), w = edges[i].w;
if(a != b)
{
sum += w;
edges[i].used = true;
p[a] = b;
}
}
}
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void build()
{
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++)
{
if(edges[i].used)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
add(a, b, w), add(b, a, w);
}
}
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
queue<int> q;
q.push(1);
while(q.size())
{
int ver = q.front();
q.pop();
for(int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if(depth[j] > depth[ver] + 1)
{
depth[j] = depth[ver] + 1;
q.push(j);
fa[j][0] = ver, d1[j][0] = w[i], d2[j][0] = -INF;
for(int k = 1; k <= 16; k ++)
{
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int distance[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
//次大值在这四个数中选即可
int dist1 = -INF, dist2 = -INF;
for(int u = 0; u < 4; u ++)
{
if(distance[u] > dist1)dist2 = dist1, dist1 = distance[u];
else if(distance[u] > dist2 && distance[u] != dist1)dist2 = distance[u];
}
d1[j][k] = dist1, d2[j][k] = dist2;
}
}
}
}
}
ll lca(int a, int b, int w)
{
if(depth[a] < depth[b])swap(a, b);
vector<int> distance;
for(int i = 16; i >= 0; i --)
{
if(depth[fa[a][i]] >= depth[b])
{
distance.push_back(d1[a][i]);
distance.push_back(d2[a][i]);
a = fa[a][i];
}
}
if(a != b)
{
for(int i = 16; i >= 0; i --)
{
if(fa[a][i] != fa[b][i])
{
distance.push_back(d1[a][i]);
distance.push_back(d2[a][i]);
distance.push_back(d1[b][i]);
distance.push_back(d2[b][i]);
a = fa[a][i], b = fa[b][i];
}
}
//别忘了把最后一步也加进去
distance.push_back(d1[a][0]);
distance.push_back(d1[b][0]);
}
int dist1 = -INF, dist2 = -INF;
for(auto d : distance)
{
if(d > dist1)dist2 = dist1, dist1 = d;
else if(d > dist2 && d != dist1)dist2 = d;
}
if(w > dist1)return w - dist1;
return w - dist2;
}
int main()
{
IOS
cin >> n >> m;
for(int i = 0; i < m; i ++)
{
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w, false};
}
sort(edges, edges + m);
for(int i = 1; i <= n; i ++)p[i] = i;
kruskal();
build();//建边
bfs();//lca初始化
ll ans = 2e18;
for(int i = 0; i < m; i ++)
{
if(!edges[i].used)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
ans = min(ans, sum + lca(a, b, w));
//lca返回w - 最大值
//最大值一定<=w,但如果等于w的话就不是"严格"次小生成树了
//如果全部边都=w,那就返回一个正无穷
//可以考虑初始化为-INF,这样如果没有严格次大值
//且全部边都=w时就会返回w - (-INF)
}
}
cout << ans;
return 0;
}