最近公共祖先
前置
1.向上标记法\(O(N)\)
2.倍增法
\(fa[i, j]\)从i开始向上走\(2^j\)步所能走到的节点。\(0 \le j \le log_{2}n\)
\(f[i, j] = f[f[i, j - 1], j - 1]\)
\(depth[]\)表示深度
二进制拼凑思想:找一个数的二进制,从高位往低位拼凑。
步骤:
1.现将两个点跳到同一层。
2.让两个点同时往上面跳,一直跳到它们最近公共最先的下一层(父亲是最近公共祖先)。
预处理\(O(nlogn)\)
查询\(O(logn)\)
祖孙询问
#include <bits/stdc++.h>
using namespace std;
const int N = 40005, M = 40005;
int n, m, cnt = - 1, rt = 1, f[N][17], dep[N];
vector<int> g[N];
void build_tree(int x, int fa)
{
f[x][0] = fa, dep[x] = dep[fa] + 1;
for (int i = 1; i <= 16; ++ i)
{
f[x][i] = f[f[x][i - 1]][i - 1];
if(!f[x][i]) break;
}
for (int i = 0; i < g[x].size(); ++ i)
{
int y = g[x][i];
if(y == fa) continue;
build_tree(y, x);
}
return ;
}
int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
for (int i = 16; i >= 0; -- i)
{
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if(x == y) return x;
for (int i = 16; i >= 0; -- i)
{
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++ i)
{
int u, v;
scanf("%d %d", &u, &v);
if(v == -1) rt = u;
else g[u].push_back(v), g[v].push_back(u);
}
build_tree(rt, 0);
int q;
scanf("%d", &q);
while(q --)
{
int u, v;
scanf("%d %d", &u ,&v);
if(u == v) printf("0\n");
else
{
int l = lca(u, v);
if(l == u) printf("1\n");
else if(l == v) printf("2\n");
else printf("0\n");
}
}
return 0;
}
距离
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
int n, q;
struct edge
{
int v, w;
};
vector<edge> g[N];
int f[N][17], dep[N], dis[N];
void build_tree(int u, int fa)
{
dep[u] = dep[fa] + 1, f[u][0] = fa;
for (int i = 1; i <= 16; ++ i)
{
f[u][i] = f[f[u][i - 1]][i - 1];
if(!f[u][i]) break;
}
for (int i = 0; i < g[u].size(); ++ i)
{
int v = g[u][i].v;
if(v == fa) continue;
dis[v] = dis[u] + g[u][i].w;
build_tree(v, u);
}
return ;
}
int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
for (int i = 16; i >= 0; -- i)
{
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if(x == y) return x;
for (int i = 16; i >= 0; -- i)
{
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main()
{
scanf("%d %d", &n, &q);
for (int i = 1; i <= n - 1; ++ i)
{
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
g[u].push_back((edge){v, w});
g[v].push_back((edge){u, w});
}
build_tree(1, 0);
while(q --)
{
int u, v;
scanf("%d %d", &u, &v);
int l = lca(u, v);
printf("%d\n", dis[u] + dis[v] - 2 * dis[l]);
}
}
次小生成树
定理:对于一张无向图,如果存在最小生成树和严格次小生成树,那么对于任何一棵最小生成树,都存在一个严格次小生成树,使得这两棵树只有一条边不同。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 300005, N = 100005;
int n, m, fd[N];
ll mit = 0;
bool vis[M];
struct edge1
{
int u, v, w;
friend bool operator < (edge1 a, edge1 b) {return a.w < b.w; }
}p[M];
struct edge2
{
int v, w;
};
vector<edge2> g[N];
int findset(int x)
{
if(x == fd[x]) return x;
else return fd[x] = findset(fd[x]);
}
void kruskal()
{
for (int i = 1; i <= n; ++ i) fd[i] = i;
sort(p + 1, p + m + 1);
int sum = 0;
for (int i = 1; i <= m; ++ i)
{
int u = findset(p[i].u), v = findset(p[i].v);
if(u == v) continue;
g[p[i].u].push_back((edge2){p[i].v, p[i].w});
g[p[i].v].push_back((edge2){p[i].u, p[i].w});
fd[u] = v, vis[i] = true, sum ++;
mit += (ll)p[i].w;
if(sum == n - 1) break;
}
return ;
}
int f[N][18], dep[N], d1[N][18], d2[N][18], val[10];
void build_tree(int x, int fa)
{
dep[x] = dep[fa] + 1, f[x][0] = fa;
for (int i = 1; i <= 17; ++ i)
{
f[x][i] = f[f[x][i - 1]][i - 1];
val[0] = d1[x][i - 1], val[1] = d1[f[x][i - 1]][i - 1], val[2] = d2[x][i - 1], val[3] = d2[f[x][i - 1]][i - 1];
sort(val, val + 3 + 1);
d1[x][i] = val[3];
for (int j = 3; j >= 0; -- j) if(val[j] != val[3]) {d2[x][i] = val[j]; break;}
if(!f[x][i]) break;
}
for (int i = 0; i < g[x].size(); ++ i)
{
int y = g[x][i].v, w = g[x][i].w;
if(y == fa) continue;
d1[y][0] = w;
build_tree(y, x);
}
return ;
}
int lca(int x, int y, int w)
{
if(dep[x] < dep[y]) swap(x, y);
int ans = 0;
for (int i = 17; i >= 0; -- i)
{
if(dep[f[x][i]] >= dep[y])
{
if(w == d1[x][i]) ans = max(ans, d2[x][i]);
else ans = max(ans, d1[x][i]);
x = f[x][i];
}
}
if(x == y) return ans;
for (int i = 17; i >= 0; -- i)
{
if(f[x][i] != f[y][i])
{
if(w == d1[x][i]) ans = max(ans, d2[x][i]);
else ans = max(ans, d1[x][i]);
if(w == d1[y][i]) ans = max(ans, d2[y][i]);
else ans = max(ans, d1[y][i]);
x = f[x][i], y = f[y][i];
}
}
if(w != d1[x][0]) ans = max(ans, d1[x][0]);
if(w != d1[y][0]) ans = max(ans, d1[y][0]);
return ans;
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; ++ i) scanf("%d %d %d", &p[i].u, &p[i].v, &p[i].w);
kruskal();
build_tree(1, 0);
ll ans = 0x7f7f7f7f7f7f7f;
for (int i = 1; i <= m; ++ i)
{
if(!vis[i] && p[i].u != p[i].v)
{
int vlu = lca(p[i].u, p[i].v, p[i].w);
if(!vlu) continue;
ans = min(ans, mit - (ll)vlu + p[i].w);
}
}
printf("%lld", ans);
return 0;
}
这代码已经很短了,汪氏打法。比网上大多数要短了。
闇の連鎖
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, val[N], ad[N], sum = 0;
vector<int> g[N];
int f[N][18], dep[N];
void build_tree(int x, int fa)
{
f[x][0] = fa, dep[x] = dep[fa] + 1;
for (int i = 1; i <= 17; ++ i)
{
f[x][i] = f[f[x][i - 1]][i - 1];
if(!f[x][i]) break;
}
for (int i = 0; i < g[x].size(); ++ i)
{
int y = g[x][i];
if(y != fa) build_tree(y, x);
}
return ;
}
int lca(int x, int y)
{
if(x == y) return x;
if(dep[x] < dep[y]) swap(x, y);
if(dep[x] != dep[y])
{
for (int i = 17; i >= 0; -- i)
{
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
}
}
if(x == y) return x;
for (int i = 17; i >= 0; -- i)
{
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
void dfs(int x, int fa)
{
for (int i = 0; i < g[x].size(); ++ i)
{
int y = g[x][i];
if(y == fa) continue;
dfs(y, x);
val[x] += val[y];
}
return ;
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n - 1; ++ i)
{
int u, v;
scanf("%d %d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
build_tree(1, 0);
for (int i = 1; i <= m; ++i)
{
int u, v;
scanf("%d %d", &u, &v);
int l = lca(u, v);
val[u] ++, val[v] ++, val[l] -= 2;
}
dfs(1, 0);
for (int i = 2; i <= n; ++ i)
{
if(!val[i]) sum += m;
else if(val[i] == 1) sum ++;
}
printf("%d", sum);
return 0;
}

浙公网安备 33010602011771号