生成树专题
uvalive3887
给定一个带权的无向图,求得一棵最小生成树,是的树中的最大边权-最小边权的差值最小
分析:当确定一个最小边时(其他的边的权值都比他大),那么之后按照kruskal算法得到的最小生成树,
此时得到的最小生成树的最大权值也肯定是最小的,因为是kruskal是按照贪心来选边的。
所以只要不断的枚举是哪条边作为最小边,然后生成最小生成树,记录所有差值中最小的一个就是答案。
时间复杂度是O(m*m*logm)
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 #pragma comment(linker, "/STACK:1024000000,1024000000") 16 typedef long long LL; 17 const int INF = 1<<30; 18 /* 19 最大边的权值减去最小边的权值 要最小的生成树, 20 即树的边权尽可能相近 21 22 枚举最小边的权值,然后生成生成树 , 然后计算权值之差, 取取最小的那一个 23 时间复杂度是m * m * logm , 24 25 26 因为生成树是 27 */ 28 const int N = 350 + 10; 29 struct Edge 30 { 31 int u, v, dis; 32 bool operator<(const Edge&rhs) 33 { 34 return dis < rhs.dis; 35 } 36 }g[N*N]; 37 int fa[N]; 38 int find(int x) 39 { 40 if (x == fa[x]) 41 return x; 42 return fa[x] = find(fa[x]); 43 } 44 int kruskal(int n, int j ,int m) 45 { 46 for (int i = 0; i <= n; ++i) 47 { 48 fa[i] = i; 49 } 50 int cnt = 0, ret = 0; 51 for (int i = j; i < m; ++i) 52 { 53 int fx = find(g[i].u); 54 int fy = find(g[i].v); 55 if (fx != fy) 56 { 57 fa[fx] = fy; 58 if (cnt == 0) 59 ret = g[i].dis; 60 if (cnt != 0 && g[i].dis - ret > ans) 61 return -1; 62 cnt++; 63 if (cnt == n - 1) 64 ret = g[i].dis - ret; 65 } 66 } 67 if (cnt == n - 1) 68 return ret; 69 return -1; 70 } 71 72 73 int main() 74 { 75 //freopen("d:/in.txt", "r", stdin); 76 int n, m; 77 while (scanf("%d",&n),n) 78 { 79 scanf("%d", &m); 80 for (int i = 0; i < m; ++i) 81 scanf("%d%d%d", &g[i].u, &g[i].v, &g[i].dis); 82 sort(g, g + m); 83 int ans = kruskal(n, 0 ,m); 84 if (ans == -1) 85 puts("-1"); 86 else 87 { 88 for (int i = 1; i < m; ++i) 89 { 90 int ret = kruskal(n, i, m); 91 if (m - i < n - 1) 92 break; 93 if (ret != -1) 94 ans = min(ans, ret); 95 } 96 printf("%d\n", ans); 97 } 98 } 99 return 0; 100 }
uvalive10600
给定一个带权无向图,求最小生成树和次小生成树
分析:只要在生成最小生成树的同时,存储这课树, 然后dfs求出树上任意两点的最大边权maxCost,
然后枚举哪条边用来替换树中的边, 然后会获得一个权值, 找到所有权值中最小的就是次小生成树了
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 #pragma comment(linker, "/STACK:1024000000,1024000000") 16 typedef long long LL; 17 const int INF = 1<<30; 18 /* 19 20 */ 21 const int N = 100 + 10; 22 struct Edge 23 { 24 int u, v, dis; 25 bool operator<(const Edge&rhs)const 26 { 27 return dis < rhs.dis; 28 } 29 }edges[N*N]; 30 bool vis[N*N]; 31 bool mark[N]; 32 vector<Edge> g[N]; 33 vector<int> st; 34 int fa[N]; 35 int maxCost[N][N]; 36 int find(int x) 37 { 38 if (x == fa[x]) 39 return x; 40 return fa[x] = find(fa[x]); 41 } 42 int kruskal(int n, int m) 43 { 44 int ret = 0, cnt = 0; 45 for (int i = 1; i <= n; ++i) 46 { 47 fa[i] = i; 48 g[i].clear(); 49 } 50 for (int i = 0; i < m; ++i) 51 { 52 int fx = find(edges[i].u); 53 int fy = find(edges[i].v); 54 if (fx != fy) 55 { 56 fa[fx] = fy; 57 vis[i] = true; 58 g[edges[i].u].push_back(edges[i]); 59 swap(edges[i].u, edges[i].v); 60 g[edges[i].u].push_back(edges[i]); 61 swap(edges[i].u, edges[i].v); 62 ret += edges[i].dis; 63 cnt++; 64 if (cnt == n - 1) 65 return ret; 66 } 67 } 68 return ret; 69 } 70 71 void dfs(int u, int fa, int dis) 72 { 73 mark[u] = true; 74 if (fa!=-1) 75 for (int i = 0; i < st.size(); ++i) 76 { 77 int x = st[i]; 78 maxCost[u][x] = maxCost[x][u] = max(maxCost[x][fa], dis); 79 } 80 st.push_back(u); 81 for (int i = 0; i < g[u].size(); ++i) 82 { 83 int v = g[u][i].v; 84 if (mark[v]) continue; 85 dfs(v, u, g[u][i].dis); 86 } 87 } 88 int main() 89 { 90 int t, n, m; 91 scanf("%d", &t); 92 while (t--) 93 { 94 scanf("%d%d", &n, &m); 95 for (int i = 0; i < m; ++i) 96 { 97 scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].dis); 98 vis[i] = false; 99 } 100 sort(edges, edges + m); 101 int ans1 = kruskal(n, m); 102 int ans2 = INF; 103 st.clear(); 104 memset(mark, 0, sizeof(mark)); 105 dfs(1, -1, 0); 106 for (int i = 0; i < m; ++i) 107 if (!vis[i]) 108 ans2 = min(ans2, ans1 - maxCost[edges[i].u][edges[i].v] + edges[i].dis); 109 printf("%d %d\n", ans1, ans2); 110 } 111 return 0; 112 }
hdu2121
给定一个带权的有向图, 问该图存不存在最小树形图;
分析:并不知道是哪一个点做根结点。 如果暴力枚举哪个点作为根结点的话,会超时。
更好的方法是设置一个虚根,让它指向所有的点,且权值设为所有边的权值之和+1(设为sum).
那么以该虚根求出最小生成树后,最小生成树的权值-sum >= sum 表示原图的最小树形图不存在
如果小于,那么就说明原图的最小树形图的权值是:最小树形图的权值-sum
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 #pragma comment(linker, "/STACK:1024000000,1024000000") 16 typedef long long LL; 17 const int INF = 1<<30; 18 /* 19 20 */ 21 struct Node 22 { 23 int u, v, dis; 24 }a[11111]; 25 int in[1111], vis[1111], id[1111], pre[1111]; 26 pair<int,int> directed_MST(int root, int n, int m) 27 { 28 pair<int, int> ret; 29 ret.first = 0; 30 while (true) 31 { 32 for (int i = 0; i < n; ++i) 33 in[i] = INF; 34 for (int i = 0; i < m; ++i) 35 { 36 //自环有什么影响?? 37 //如果有自环,且权值足够小, 那么就不会选择别的点指向自己的边 38 //那么就要循环到别的点指向自己的边的权值足够小时,才会被选择 39 if (a[i].u!=a[i].v &&a[i].dis < in[a[i].v]) 40 { 41 in[a[i].v] = a[i].dis; 42 pre[a[i].v] = a[i].u; 43 if (a[i].u == root) 44 ret.second = i;//不能直接存储点,应该点会被重新编号 45 } 46 } 47 for (int i = 0; i < n; ++i) 48 if (i != root &&in[i] == INF){ 49 ret.first = -1; 50 return ret; 51 } 52 in[root] = 0; 53 for (int i = 0; i < n; ++i) 54 { 55 vis[i] = id[i] = -1; 56 } 57 int cnt = 0; 58 for (int i = 0; i < n; ++i) 59 { 60 ret.first += in[i]; 61 int v = i; 62 while (v != root && vis[v] != i && id[v] == -1) 63 { 64 vis[v] = i; 65 v = pre[v]; 66 } 67 if (v != root && id[v] == -1) 68 { 69 for (int u = pre[v]; u != v; u = pre[u]) 70 id[u] = cnt; 71 id[v] = cnt++; 72 } 73 } 74 if (cnt == 0) break; 75 for (int i = 0; i < n;++i) 76 if (id[i] == -1) id[i] = cnt++; 77 for (int i = 0; i < m; ++i) 78 { 79 int v = a[i].v; 80 a[i].u = id[a[i].u]; 81 a[i].v = id[a[i].v]; 82 if (a[i].u != a[i].v) 83 a[i].dis -= in[v]; 84 } 85 root = id[root]; 86 n = cnt; 87 } 88 return ret; 89 } 90 int main() 91 { 92 int n, m, sum; 93 while (scanf("%d%d", &n, &m) != EOF) 94 { 95 sum = 0; 96 for (int i = 0; i < m; ++i) 97 { 98 scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].dis); 99 sum += a[i].dis; 100 } 101 sum++; 102 for (int i = m; i < n + m; ++i) 103 { 104 a[i].u = n; 105 a[i].v = i - m; 106 a[i].dis = sum; 107 } 108 pair<int, int> ret = directed_MST(n, n + 1, n + m); 109 if (ret.first - sum >= sum) 110 puts("impossible"); 111 else 112 printf("%d %d\n", ret.first - sum, ret.second - m); 113 puts(""); 114 } 115 return 0; 116 }
spoj Find The Determinant III
给一个矩阵, 要我们求矩阵的行列式模p之后的值。
只要将矩阵转为为上三角矩阵,那么行列式的值就是对角线上的元素相乘,再乘以1或者-1即可
要将矩阵转为上三角, 那么问题是给定两行数,如果将一行数字的第一个数字变为0, 我们可以运用欧几里得的性质,让大的数字不断减去小的数字, 那么肯定有一个数字会变成0
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 #pragma comment(linker, "/STACK:1024000000,1024000000") 16 typedef long long LL; 17 const int INF = 1<<30; 18 /* 19 20 */ 21 LL g[222][222]; 22 LL solve(int n, int MOD) 23 { 24 LL ret = 1; 25 for (int i = 1; i <= n; ++i) 26 { 27 for (int j = i + 1; j <= n; ++j) 28 { 29 while (g[j][i]) 30 { 31 //下面就是欧几里得过程了, 让大的数字减去小的,然后交换 32 LL t = g[i][i] / g[j][i]; 33 for (int k = i; k <= n; ++k) 34 g[i][k] = (g[i][k] - g[j][k] * t) % MOD; 35 for (int k = i; k <= n; ++k) 36 swap(g[i][k], g[j][k]); 37 ret = -ret; 38 } 39 } 40 if (g[i][i] == 0) return 0; 41 ret = ret * g[i][i] % MOD; 42 } 43 44 return (ret%MOD + MOD) % MOD; 45 } 46 int main() 47 { 48 int n, p; 49 while (scanf("%d%d", &n, &p) != EOF) 50 { 51 for (int i = 1; i <= n; ++i) 52 for (int j = 1; j <= n; ++j) 53 scanf("%lld", &g[i][j]); 54 printf("%lld\n", solve(n, p)); 55 } 56 return 0; 57 }
spoj Highways 生成树计数
给定一个无向图,问生成树的个数有多少个
运用matrix_tree定理即可。
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 typedef long long LL; 16 const int INF = 1<<30; 17 /* 18 给定一个无向图, 问与多少中删边的方式,形成一棵树 19 C = A - G 20 */ 21 const int N = 105; 22 LL C[N][N]; 23 int degree[N]; 24 LL det(int n) 25 { 26 LL ret = 1; 27 for (int i = 1; i < n; ++i) 28 { 29 for (int j = i + 1; j < n; ++j) 30 { 31 while (C[j][i]) 32 { 33 LL t = C[i][i] / C[j][i]; 34 for (int k = i; k < n; ++k) 35 C[i][k] = C[i][k] - C[j][k] * t; 36 for (int k = i; k < n; ++k) 37 swap(C[i][k], C[j][k]); 38 } 39 } 40 if (C[i][i] == 0)return 0; 41 ret = ret *C[i][i]; 42 } 43 if (ret < 0) ret = -ret;; 44 return ret; 45 } 46 47 48 int main() 49 { 50 int t, n, m; 51 int u, v; 52 scanf("%d", &t); 53 while (t--) 54 { 55 memset(degree, 0, sizeof(degree)); 56 memset(C, 0, sizeof(C)); 57 scanf("%d%d", &n, &m); 58 for (int i = 0; i < m; ++i) 59 { 60 scanf("%d%d", &u, &v); 61 C[u][v] = C[v][u] = -1; 62 degree[u]++; 63 degree[v]++; 64 } 65 for (int i = 1; i <= n; ++i) 66 C[i][i] = degree[i]; 67 printf("%lld\n", det(n)); 68 } 69 return 0; 70 }
hdu4408 最小生成树计数
给定一个带权无向图, 问最小生成树的个数
因为带权, 所以需要变通,然后使用matrix_tree定理。 如
可以将kruskal处理相同权值的边的过程看成一个阶段, 那么这个阶段就是求一棵树的过程, 这个阶段结束后, 这个阶段的结果就缩成了一个点。
所以只要将每个阶段都运用matrix_tree定理, 然后每个阶段的结果相乘, 那么就得到答案了。
这题有坑, n=1的时候, 是输出0,而不是1。 被坑了一晚上
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <iostream> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <map> 10 #include <set> 11 #include <string> 12 #include <math.h> 13 using namespace std; 14 #pragma warning(disable:4996) 15 #pragma comment(linker, "/STACK:1024000000,1024000000") 16 typedef __int64 LL; 17 const int INF = 1 << 30; 18 /* 19 对每一个阶段进行matrix_tree定理 然后把所有阶段的结果相乘 20 */ 21 const int N = 111; 22 const int M = 1111; 23 int n, m, p; 24 struct Node 25 { 26 int u, v, dis; 27 bool operator<(const Node&rhs)const 28 { 29 return dis < rhs.dis; 30 } 31 }edges[M]; 32 LL f1[N], f2[N] 33 /* 34 f1用来缩点使用, 如果f1[i]相等, 说明这这些点都缩成了以父亲为编号的点 35 */ 36 LL G[N][N], C[N][N]; 37 vector<int> g[N]; 38 LL vis[N]; 39 int find(int x, LL f[]) 40 { 41 if (x == f[x]) 42 return x; 43 return find(f[x], f); 44 } 45 46 LL det(LL a[][N], int n) 47 { 48 int ret = 1; 49 for (int i = 1; i < n; ++i) 50 { 51 for (int j = i + 1; j < n; ++j) 52 { 53 while (a[j][i]) 54 { 55 int t = a[i][i] / a[j][i]; 56 for (int k = i; k < n; ++k) 57 a[i][k] = (a[i][k] - a[j][k] * t) % p; 58 for (int k = i; k < n; ++k) 59 swap(a[i][k], a[j][k]); 60 ret = -ret; 61 } 62 } 63 if (a[i][i] == 0) return 0; 64 ret = (ret*a[i][i]) % p; 65 } 66 return (ret + p) % p; 67 } 68 void solve() 69 { 70 71 LL ans = 1; 72 LL dis = edges[0].dis; 73 int fx, fy; 74 for (int i = 1; i <= n; ++i) 75 { 76 f1[i] = f2[i] = i; 77 vis[i] = false; 78 } 79 for (int i = 0; i <= m; ++i) 80 { 81 if (edges[i].dis != dis || i == m) 82 { 83 for (int j = 1; j <= n; ++j) 84 { 85 if (vis[j])//找到这一阶段权值相等的边 86 { 87 g[find(j, f2)].push_back(j); 88 vis[j] = false; 89 } 90 } 91 for (int j = 1; j <= n; ++j) 92 { 93 if (g[j].size() > 1)//枚举每一个连通分量,至少有两个点才说明有边 94 { 95 int len = g[j].size(); 96 for (int a = 0; a < len; ++a) 97 for (int b = 0; b < len; ++b) 98 C[a][b] = 0; 99 for (int a = 0; a < len; ++a) 100 for (int b = a + 1; b < len; ++b) 101 { 102 fx = g[j][a]; 103 fy = g[j][b]; 104 C[a][b] = (C[b][a] -= G[fx][fy]); 105 C[a][a] += G[fx][fy]; 106 C[b][b] += G[fx][fy]; 107 } 108 LL ret = det(C, len); 109 ans = (ans*ret) % p; 110 111 //缩点 112 for (int a = 0; a < len; ++a) 113 f1[g[j][a]] = j; 114 } 115 116 } 117 for (int j = 1; j <= n; ++j) 118 g[j].clear(); 119 if (i == m)break; 120 121 dis = edges[i].dis; 122 } 123 124 125 //找到不在一个集合里面的,权值相等的所有边。 126 fx = find(edges[i].u, f1); 127 fy = find(edges[i].v, f1); 128 if (fx == fy) continue; 129 //这些边用vis和并查集来存储 130 vis[fx] = vis[fy] = true; 131 f2[find(fx, f2)] = find(fy, f2); 132 G[fx][fy]++; 133 G[fy][fx]++; 134 135 } 136 bool flag = true; 137 if ( m == 0) flag = false;//这里最坑了。 n=1的时候,是输出0,而不是输出1 138 for (int i = 1; i <= n; ++i) 139 f2[i] = find(i, f2); 140 for (int i = 2; i <= n&&flag; ++i) 141 if (f2[i] != f2[i - 1]) 142 flag = false; 143 if (flag) 144 printf("%I64d\n", ans); 145 else 146 printf("0\n"); 147 } 148 int main() 149 { 150 while (scanf("%d%d%d", &n, &m, &p), n + m + p) 151 { 152 memset(G, 0, sizeof(G)); 153 for (int i = 0; i < m; ++i) 154 scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].dis); 155 sort(edges, edges + m); 156 solve(); 157 } 158 return 0; 159 }
Find The Determinant III