main
黄海 2 years ago
parent d8fce2f6ae
commit 905eaec715

@ -1,163 +0,0 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, M = 300010;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int f[N][16]; // f[i][k]表示树上的某节点i向上走2^k步到达的节点
PII d[N][16]; // d[i][k]表示树上的某节点i向上走2^k步到达的节点最长距离和次长距离
int depth[N]; // 深度数组
// Kruskal用的结构体
struct Edge {
int a, b, c; // 从a到b边权为c
bool flag; // 是不是最小生成树的树边
const bool operator<(const Edge &ed) const {
return c < ed.c;
}
} edge[M];
// 邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
// 并查集
int p[N];
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
// 树上倍增求任意两点最短距离
int bfs(int root) {
queue<int> q;
q.push(root);
depth[root] = 1;
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!depth[j]) {
q.push(j);
depth[j] = depth[u] + 1; // 记录深度
f[j][0] = u; // 记录2^0->t,描述父节点
// 下面是与普通的倍增不一样的代码,记录两点间最大长度与次大长度
d[j][0] = {w[i], 0}; // j->t 的最大距离与次大距离
for (int k = 1; k <= 15; k++) { // 倍增
int v = f[j][k - 1]; // 设j跳2 ^(k-1)到达的是v点
f[j][k] = f[v][k - 1]; // v点跳 2^(k-1)到达的终点就是j跳2^k的终点
// ①最大边权一定是两个线段中最长边权的最大值
d[j][k].first = max(d[j][k - 1].first, d[v][k - 1].first);
// ②次大边权分情况讨论
// 如果前半段的最大值 小于 后半段的最大值
// 次大值 等于 max(前半段最大值,后半段次大值)
if (d[j][k - 1].first == d[v][k - 1].first)
// 次大值 等于 max(前半段次大值,后半段次大值)
d[j][k].second = max(d[j][k - 1].second, d[v][k - 1].second);
// 如果前半段的最大值 等于 后半段的最大值
else if (d[j][k - 1].first < d[v][k - 1].first)
d[j][k].second = max(d[j][k - 1].first, d[v][k - 1].second);
// 如果前半段的最大值 大于 后半段的最大值
else // 次大值 等于 max(前半段次大值,后半段的最大值)
d[j][k].second = max(d[j][k - 1].second, d[v][k - 1].first);
}
}
}
}
}
// 因为同时需要同步修改最大值和次大值,所以采用了地址符&引用方式定义参数
// m1:最大值m2次大值
void cmp(int &m1, int &m2, PII x) {
if (m1 == x.first)
m2 = max(m2, x.second);
else if (m1 < x.first)
m2 = max(m1, x.second), m1 = x.first;
else
m2 = max(m2, x.first);
}
// 最近公共祖先
// 由a->b的边,边权是w
// 返回值如果加上这条边w去掉最小生成树中的某条边(m1或m2),得到一个待选的次小生成树
// 此时的 w- m1 或者 w-m2的值是多少。
// 具体是-m1,还是-m2要区别对待因为如果w=m1,就是-m2,否则就是-m1
// 利用倍增的思想对bfs已经打好的表 d数组和f数组 进行快速查询
// 找出a->b之间的最大距离和次大距离
int lca(int a, int b, int w) {
if (depth[a] < depth[b]) swap(a, b); // 保证a的深度大于b的深度
int m1 = -1e18, m2 = -1e18; // 最大边,次大边初始化
for (int k = 15; k >= 0; k--) // 由小到大尝试
if (depth[f[a][k]] >= depth[b]) { // 让a向上跳2^k步
cmp(m1, m2, d[a][k]); // a向上跳2^k步时走过的路径中可能存在最大边或次大边
a = f[a][k]; // 标准的lca
}
// 当a与b不是同一个点时此时两者必须是depth一样的情况同时向上查询2^k必然可以找到LCA
if (a != b) {
for (int k = 15; k >= 0; k--)
if (f[a][k] != f[b][k]) {
cmp(m1, m2, d[a][k]); // a向上跳2^k步时走过的路径中可能存在最大边或次大边
cmp(m1, m2, d[b][k]); // b向上跳2^k步时走过的路径中可能存在最大边或次大边
a = f[a][k], b = f[b][k];
}
// 此时a和b到lca下同一层 所以还要各跳1步=跳2^0步
// 联想一下在普通版本LCA中的最终返回值就明白了
// return f[a][0];
cmp(m1, m2, d[a][0]);
cmp(m1, m2, d[b][0]);
}
return w == m1 ? w - m2 : w - m1;
}
int main() {
int n, m, a, b, c;
scanf("%d %d", &n, &m);
// 并查集初始化
for (int i = 1; i <= n; i++) p[i] = i;
// 邻接表初始化
memset(h, -1, sizeof h);
// Kruskal
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &a, &b, &c);
edge[i] = {a, b, c, false};
}
// 按边权排序+最小生成树
sort(edge, edge + m);
LL sum = 0, ans = 1e18;
for (int i = 0; i < m; i++) {
a = find(edge[i].a), b = find(edge[i].b), c = edge[i].c;
if (a != b) {
p[a] = b;
sum += c; // 最小生成树的边权总和
edge[i].flag = true; // 标识为最小生成树中的边
// 将最小生成树中的树边单独构建一个图出来
add(edge[i].a, edge[i].b, c), add(edge[i].b, edge[i].a, c);
}
}
// 倍增预处理,记录任意点向上2^k步的最大值次大值深度等信息后面lca会用到
bfs(1);
// 用非树边去尝试替换最小生成树中的边然后取min
// lca查表
for (int i = 0; i < m; i++)
if (!edge[i].flag) { // 枚举非树边
a = edge[i].a, b = edge[i].b, c = edge[i].c;
ans = min(ans, sum + lca(a, b, c));
}
// 输出
printf("%lld\n", ans);
return 0;
}

@ -1,83 +0,0 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 510, M = 10010;
int n, m;
struct Edge {
int a, b, w;
bool f;
bool operator<(const Edge &t) const {
return w < t.w;
}
} edge[M];
int p[N];
int dist1[N][N], dist2[N][N];
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[]) {
d1[u] = maxd1, d2[u] = maxd2;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j != fa) {
int td1 = maxd1, td2 = maxd2;
if (w[i] > td1)
td2 = td1, td1 = w[i];
else if (w[i] < td1 && w[i] > td2)
td2 = w[i];
dfs(j, u, td1, td2, d1, d2);
}
}
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edge[i] = {a, b, w};
}
sort(edge, edge + m);
for (int i = 1; i <= n; i++) p[i] = i;
LL sum = 0;
for (int i = 0; i < m; i++) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
int pa = find(a), pb = find(b);
if (pa != pb) {
p[pa] = pb;
sum += w;
add(a, b, w), add(b, a, w);
edge[i].f = true;
}
}
for (int i = 1; i <= n; i++) dfs(i, -1, -1e9, -1e9, dist1[i], dist2[i]);
LL res = 1e18;
for (int i = 0; i < m; i++)
if (!edge[i].f) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
LL t;
if (w > dist1[a][b])
t = sum + w - dist1[a][b];
else if (w > dist2[a][b])
t = sum + w - dist2[a][b];
res = min(res, t);
}
printf("%lld\n", res);
return 0;
}

@ -1,21 +1,22 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define int long long
#define endl "\n"
const int N = 510, M = 10010;
int n, m;
// 结构体
struct Edge {
int a, b, w;
int a, b, c;
bool flag; // 是不是最小生成树中的边
bool operator<(const Edge &t) const {
return w < t.w;
return c < t.c;
}
} edge[M]; // 因为本题需要用链式前向星建图所以避开了使用e做为边的数组名称
int d1[N][N]; // 从i出发到达j最短距离
int d2[N][N]; // 从i出发到达j次短距离
LL sum; // 最小生成树的边权和
int sum; // 最小生成树的边权和
// 邻接表
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {
@ -55,14 +56,14 @@ void dfs(int s, int u, int fa, int m1, int m2) {
}
}
int main() {
scanf("%d %d", &n, &m);
signed main() {
cin >> n >> m;
// 初始化邻接表
memset(h, -1, sizeof h);
// Kruskal + 建图
for (int i = 0; i < m; i++)
scanf("%d %d %d", &edge[i].a, &edge[i].b, &edge[i].w);
cin >> edge[i].a >> edge[i].b >> edge[i].c;
// 按边权由小到大排序
sort(edge, edge + m);
@ -72,15 +73,15 @@ int main() {
// Kruskal求最小生成树
for (int i = 0; i < m; i++) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
int a = edge[i].a, b = edge[i].b, c = edge[i].c;
int pa = find(a), pb = find(b);
if (pa != pb) {
p[pa] = pb; // 并查集合并
// ①最小生成树的边权和
sum += w;
sum += c;
// ②最小生成树建图,无向图,为求最小生成树中任意两点间的路径中最大距离、次大距离做准备
add(a, b, w), add(b, a, w);
add(a, b, c), add(b, a, c);
// ③标识此边为最小生成树中的边,后面需要枚举每条不在最小生成树中的边
edge[i].flag = 1;
}
@ -90,19 +91,18 @@ int main() {
// 换根以每个点为根进行dfs,可以理解为枚举了每一种情况,肯定可以获取到任意两点间的最长路径和严格次长路径
for (int i = 1; i <= n; i++) dfs(i, i, 0, -1e18, -1e18);
LL res = 1e18; // 预求最小,先设最大
int res = 1e18; // 预求最小,先设最大
// 枚举所有不在最小生成树中的边,尝试加入a->b的这条直边
for (int i = 0; i < m; i++)
if (!edge[i].flag) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
if (w > d1[a][b]) // 最小生成树外的一条边,(a-b),如果比最小生成树中a-b的最长边长就有机会参加评选次小生成树。
int a = edge[i].a, b = edge[i].b, c = edge[i].c;
if (c > d1[a][b]) // 最小生成树外的一条边,(a-b),如果比最小生成树中a-b的最长边长就有机会参加评选次小生成树。
// 最终的选举结果取决于它增加的长度是不是最少的
res = min(res, sum + w - d1[a][b]); // 替换最大边
else if (w > d2[a][b]) // 替换严格次大边
res = min(res, sum + w - d2[a][b]); // 严格次小生成树的边权和
res = min(res, sum + c - d1[a][b]); // 替换最大边
else if (c > d2[a][b]) // 替换严格次大边
res = min(res, sum + c - d2[a][b]); // 严格次小生成树的边权和
}
// 输出
printf("%lld\n", res);
return 0;
}

@ -29,8 +29,8 @@
**数据范围**
$1≤N≤500$,
$1≤M≤10$4$,
$1≤z≤ 10 ^9$,
$1≤M≤10^4$,
$1≤z≤10^9$,
数据中可能包含重边。
**输入样例**

@ -172,4 +172,9 @@ $Kruskal$的简单应用,先把必选的边放到并查集中,然后将可
那么,对于两个家族的其它成员而言,要想形成完全图,就需要笛卡尔积条边,对了,还需要把这条最小生成树的边去掉才行。
- 加上去的那些边,条边最小都需要比当前枚举到的边长大$1$才行,因为这样才能保证求出的是唯一最小生成树,并且这种补全办法的成本最低!
**知识点**
① 并查集+维护个数
② 逆向思维
③ 最小生成树$Kruskal$算法
AcWing 1148. 秘密的牛奶运输
Loading…
Cancel
Save