diff --git a/TangDou/AcWing/MinimalSpanningTree/1148_lca.cpp b/TangDou/AcWing/MinimalSpanningTree/1148_lca.cpp deleted file mode 100644 index a87942d..0000000 --- a/TangDou/AcWing/MinimalSpanningTree/1148_lca.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include -using namespace std; -typedef long long LL; -const int N = 100010, M = 300010; -const int INF = 0x3f3f3f3f; -typedef pair 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 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; -} \ No newline at end of file diff --git a/TangDou/AcWing/MinimalSpanningTree/1148_yxc.cpp b/TangDou/AcWing/MinimalSpanningTree/1148_yxc.cpp deleted file mode 100644 index 15c58c7..0000000 --- a/TangDou/AcWing/MinimalSpanningTree/1148_yxc.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -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; -} diff --git a/TangDou/AcWing/MinimalSpanningTree/1148_dfs.cpp b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.cpp similarity index 80% rename from TangDou/AcWing/MinimalSpanningTree/1148_dfs.cpp rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.cpp index fd9f55b..d9c5816 100644 --- a/TangDou/AcWing/MinimalSpanningTree/1148_dfs.cpp +++ b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.cpp @@ -1,21 +1,22 @@ #include 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; } \ No newline at end of file diff --git a/TangDou/AcWing/MinimalSpanningTree/1148.drawio b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.drawio similarity index 100% rename from TangDou/AcWing/MinimalSpanningTree/1148.drawio rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.drawio diff --git a/TangDou/AcWing/MinimalSpanningTree/1148.md b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.md similarity index 97% rename from TangDou/AcWing/MinimalSpanningTree/1148.md rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.md index a89f16c..8793f43 100644 --- a/TangDou/AcWing/MinimalSpanningTree/1148.md +++ b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/1148.md @@ -29,8 +29,8 @@ **数据范围** $1≤N≤500$, -$1≤M≤10$4$, -$1≤z≤ 10 ^9$, +$1≤M≤10^4$, +$1≤z≤10^9$, 数据中可能包含重边。 **输入样例**: diff --git a/TangDou/AcWing/MinimalSpanningTree/346.cpp b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.cpp similarity index 100% rename from TangDou/AcWing/MinimalSpanningTree/346.cpp rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.cpp diff --git a/TangDou/AcWing/MinimalSpanningTree/346.eddx b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.eddx similarity index 100% rename from TangDou/AcWing/MinimalSpanningTree/346.eddx rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.eddx diff --git a/TangDou/AcWing/MinimalSpanningTree/346.md b/TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.md similarity index 100% rename from TangDou/AcWing/MinimalSpanningTree/346.md rename to TangDou/AcWing_TiGao/T3/MinialSpanningTree/346.md diff --git a/TangDou/Topic/【最小生成树】专题.md b/TangDou/Topic/【最小生成树】专题.md index 473d222..02a78f2 100644 --- a/TangDou/Topic/【最小生成树】专题.md +++ b/TangDou/Topic/【最小生成树】专题.md @@ -172,4 +172,9 @@ $Kruskal$的简单应用,先把必选的边放到并查集中,然后将可 那么,对于两个家族的其它成员而言,要想形成完全图,就需要笛卡尔积条边,对了,还需要把这条最小生成树的边去掉才行。 - 加上去的那些边,条边最小都需要比当前枚举到的边长大$1$才行,因为这样才能保证求出的是唯一最小生成树,并且这种补全办法的成本最低! +**知识点** +① 并查集+维护个数 +② 逆向思维 +③ 最小生成树$Kruskal$算法 + AcWing 1148. 秘密的牛奶运输 \ No newline at end of file