生成树专题

生成树专题地址

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
", ans);
 97         }
 98     }
 99     return 0;
100 }
View Code

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
", ans1, ans2);
110     }
111     return 0;
112 }
View Code

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
", ret.first - sum, ret.second - m);
113         puts("");
114     }
115     return 0;
116 }
View Code

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
", solve(n, p));
55     }
56     return 0;
57 }
View Code

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
", det(n));
68     }
69     return 0;
70 }
View Code

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
", ans);
145     else
146         printf("0
");
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 }
View Code

Find The Determinant III

原文地址:https://www.cnblogs.com/justPassBy/p/4688135.html