题意:
给出一个无向图,求一个生成树使得这个生成树的最大边与最小边之差最小,输出这个最小的差值。n的最大值为350。
思路:
这题不看题解想破头也不知道怎么写Orz。
暴力的做法是可以从大到小枚举边作为最小边的权值,求MST,但是复杂度达到了O(n^4),很显然会T。
考虑在kruskal算法加边的时候,当两个点在同一个连通分量的时候,加入这条边会形成环,这个时候就把环中的最小边去掉,剩下的边就尽可能达到了最大,当前加入的边设为Ea也最大的,然后再找现在生成树中的最小边Eb;当有n-1条边的时候,就形成了一个生成树,就可以利用Ea – Eb去更新答案。
最重要的两个过程是找环和加边。
判断环,用了LCA的思想,但并不是倍增,而是纯粹暴力的dfs。对于一条边的两个点假设为x和y,首先从一个点x开始访问,直到访问到它的祖先,即满足条件par[x] == x,标记这个过程中的所有点;然后从y开始访问,直到访问到它的祖先(同上)或者它自己或某一个祖先被标记了为止,若y访问到祖先都没有点被标记,那么就说明x和y没有公共祖先,那么它们就不在同一个连通分量中,加入这条边就不会形成环。
若找到了环,那么就要求这个环中边的最小值,求法很自然,遍历从x出发到lca的所有边,遍历从y出发到lca的所有边,找出其中的最小边,之后去掉这条边,去掉这条边有个技巧,假设最小边的非父亲点为u,那么去掉这条边时,令par[u] = u,这条边就被去掉了。之后再找最小边即可。
之后就是加边的过程。并查集加边是启发式合并,但是这题的加边是有方向的加边。
假设两个点x和y,如果有其中一个点就是祖先,那么就把这个点连到另外一个点上(如x为祖先点,那么令par[x] = y)。
但是如果两个点都不是祖先,那么就要考虑如何合并,因为一个点不可能同时有两个父亲。
可以考虑从一个点开始访问,直到访问到祖先,将路径上的点全部按访问的顺序记录在一个vector中,之后逆向加边。
再把这个点连到另一个点上。如图:
合并前:
合并后:
最后再把这个点(从这个点开始访问的)连接到另一个点上,加边就完成了。
枚举边的复杂度为O(n^2),找环和加边的复杂度为O(n),所以总的复杂度为O(n^3)。
感谢MZjj帮我debug!
代码:
1 #include <stdio.h> 2 #include <string.h> 3 #include <iostream> 4 #include <algorithm> 5 #include <vector> 6 using namespace std; 7 8 const int N = 400; 9 const int inf = 0x3f3f3f3f; 10 11 struct edge 12 { 13 int x,y,w; 14 15 edge(){}; 16 17 edge(int a,int b,int c) 18 { 19 x = a; 20 y = b; 21 w = c; 22 } 23 }; 24 25 vector<edge> es; 26 27 int par[N]; 28 int n,m; 29 int dis[N][N]; 30 bool vis[N]; 31 int num; 32 int Minedge; 33 struct edge minedge; 34 35 bool cmp(edge aa,edge bb) 36 { 37 return aa.w < bb.w; 38 } 39 40 void init(void) 41 { 42 for (int i = 0;i < n;i++) par[i] = i; 43 44 num = 0; 45 46 es.clear(); 47 48 memset(dis,inf,sizeof(dis)); 49 50 Minedge = inf; 51 } 52 53 int LCA(int x,int y) 54 { 55 memset(vis,0,sizeof(vis)); 56 57 while (1) 58 { 59 vis[x] = 1; 60 61 if (x == par[x]) break; 62 63 x = par[x]; 64 } 65 66 while (!vis[y] && y != par[y]) 67 { 68 y = par[y]; 69 } 70 71 if (!vis[y]) return -1; 72 73 return y; 74 } 75 76 void findcycle(int k) 77 { 78 int x = es[k].x,y = es[k].y; 79 80 int lca = LCA(x,y); 81 82 if (lca == -1) return; 83 84 minedge.w = inf; 85 86 while (x != par[x] && x != lca) 87 { 88 if (dis[x][par[x]] < minedge.w) 89 { 90 minedge = edge(par[x],x,dis[x][par[x]]); 91 } 92 93 x = par[x]; 94 } 95 96 while (y != par[y] && y != lca) 97 { 98 if (dis[y][par[y]] < minedge.w) 99 { 100 minedge = edge(par[y],y,dis[y][par[y]]); 101 } 102 103 y = par[y]; 104 } 105 106 par[minedge.y] = minedge.y; 107 108 Minedge = inf; 109 110 for (int i = 0;i < n;i++) 111 { 112 Minedge = min(dis[par[i]][i],Minedge); 113 } 114 115 num--; 116 } 117 118 void adde(int k) 119 { 120 int x = es[k].x,y = es[k].y; 121 122 if (x == par[x]) par[x] = y; 123 else if (y == par[y]) par[y] = x; 124 else 125 { 126 vector<int> v; 127 128 while (1) 129 { 130 v.push_back(x); 131 132 if (x == par[x]) break; 133 134 x = par[x]; 135 } 136 137 for (int i = v.size() - 1;i > 0;i--) par[v[i]] = v[i-1]; 138 139 par[es[k].x] = es[k].y; 140 } 141 142 num++; 143 144 Minedge = min(Minedge,es[k].w); 145 } 146 147 int main() 148 { 149 while (scanf("%d",&n) && n) 150 { 151 scanf("%d",&m); 152 153 init(); 154 155 for (int i = 0;i < m;i++) 156 { 157 int a,b,c; 158 159 scanf("%d%d%d",&a,&b,&c); 160 161 es.push_back(edge(a,b,c)); 162 } 163 164 for (int i = 0;i < m;i++) 165 { 166 int x = es[i].x,y = es[i].y; 167 168 dis[x][y] = dis[y][x] = es[i].w; 169 } 170 171 sort(es.begin(),es.end(),cmp); 172 173 int ans = inf; 174 175 for (int i = 0;i < m;i++) 176 { 177 findcycle(i); 178 adde(i); 179 180 if (num == n - 1) 181 { 182 //cout << es[i].w << " ** " << Minedge << endl; 183 ans = min(ans,es[i].w - Minedge); 184 } 185 } 186 187 cout << ans << endl; 188 } 189 190 return 0; 191 }