1.(严格)次小生成树
解法:1.依次删除树上的边后求最小生成树,判生成树唯一时只需删存在与它长度相同的边,严格次小生成树只需删没有与自己长度相同的边。
2.以最小生成树上每个点为根dfs,求出树上任意两点之间的最长边。枚举所有不在最小生成树上的边,将其添加到树上必定形成一个环,去掉环上的最长边形成的生成树最小。判断生成树是否唯一,求次小生成树严格次小生成树等均在此处处理。
void work(){ best=new int[n][n]; vis=new boolean[n]; for(int i=0;i<n;i++){ Arrays.fill(vis, false); dfs(i,i); } int min=99999999; for(int i=0;i<m;i++) if(!ed[i].flag) if(mst+ed[i].v-best[ed[i].a][ed[i].b]<min) min=mst+ed[i].v-best[ed[i].a][ed[i].b]; } void dfs(int v,int a){ vis[a]=true; for(int i=E[a];i!=-1;i=buf[i].ne){ int b=buf[i].be; if(!vis[b]){ best[v][b]=Math.max(best[v][a], buf[i].dis); dfs(v,b); } } }
题目:Ural 1416. Confidential 要求最小生成树和次小生成树,注意判连通性。
Poj 1679 The Unique MST判断最小生成树是否唯一
2.最小度限制生成树
把顶点V的度数<= K 称做度限制条件,把满足这一条件的生成树叫做度限制生成树,把权值和最小的度限制生成树称最小度限制生成树。
解法:1.把顶点去除,对其他点边使用kruskal求生成树,并求出共有m个连通分量。
2.一次添加顶点到各个连通分量的最短边,得到m度最小生成树,若m>k则不存在k度生成树。
3.dfs求出m度生成树上顶点到各个点间路径上的最长边best[i]。
4.依次次遍历从顶点出发的为添加到m度最小生成树的边,求出best[ed[j].a] - ed[j].dis>0且最大的一条边,添加到k度生成树中。
5.重复4知道m==k或不存在那样一条边。
题目:poj 1639 Picnic Planning
3.最优比例生成树
一个带权完全图,每条边都有自己的花费值cost[i]和 长度dis[i],求一个生成树,使得r=(∑cost[i]*x[i] ) / (∑dis[i]*x[i] )最小。
解法:详见《0-1分数规划问题》
题目:poj2526 Desert King
4.最小树形图
有向图的最小生成树,并且规定了起点。
解法:1.首先dfs判断一下起点可达其他任意点,否则不存在树形图。
2.为每个点找一条最小的入边,如果没环那么这些边就构成了最小树形图,转入4;否则转入3.
3.将环上每条边的边权加入到ans中,同时形成新的点new,对于环内多有的点i,如果存在边<j,i>则<j,new>的边权等于所有 <j,i>-<pre[i],i>中最小的(因为缩点后再次构图必须从环中去除一条边<pre[i],i>再添加一条最小边<x,i>,这样就可以保证答案的正确性,很巧妙,换个图就很清晰了),<new,j>的边权=所有<i,j>的最小值,缩点完成,转向2.
4.缩点后n个点,n-1条边,切无环,此时就是一颗连通树了,ans+=这n-1条边的边权记得答案;
以上是国人发明的“朱刘算法”,邻接矩阵复杂度(n ^3)临界表复杂度(VE)。
对于跟不固定的情况,wy教主有一巧妙方法,今后再去实现。摘录如下:
新加一个点,和每个点连权相同的边,这个权大于原图所有边权的和,这样这个图固定跟的最小树形图和原图不固定跟的最小树形图就是对应的了。
题目:Poj3164 Command Network
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.util.Arrays; public class Command3164 { class point { int x, y; double dis(point o) { double temp = (x - o.x) * (x - o.x) + (y - o.y) * (y - o.y); return Math.sqrt(temp); } } double map[][] = new double[110][110], inf = 99999999.99; int pre[] = new int[110]; boolean vis[] = new boolean[110], is[] = new boolean[110]; void build() { for (int i = 2; i <= n; i++) if (!vis[i]) { pre[i] = 1; for (int j = 2; j <= n; j++) if (i != j && !vis[j] && map[pre[i]][i] > map[j][i]) pre[i] = j; } } boolean bcc() { for (int i = 2; i <= n; i++) { if (vis[i]) continue; Arrays.fill(is, false); int j; for (j = i; j != 1 && !is[j]; j = pre[j]) is[j] = true; if (j == 1) continue;// j存在自环,环内所有点所为j ans += map[pre[j]][j]; for (i= pre[j]; i!= j; i = pre[i]) { ans += map[pre[i]][i]; vis[i] = true; }//缩点 for (int k = 1; k <= n; k++) if (!vis[k] && map[k][j] < inf) map[k][j] -= map[pre[j]][j]; for (i = pre[j]; i!= j; i = pre[i])// 处理环内所有点的边 for (int k = 1; k <= n; k++) if (!vis[k]) { map[j][k] = Math.min(map[j][k], map[i][k]);// 出边 if (map[k][i] < inf) map[k][j] = Math.min(map[k][j], map[k][i]// 入边 - map[pre[i]][i]); } return true; } return false; } void dfs(int x) { vis[x] = true; cnt++; for (int i = 1; i <= n; i++) if (!vis[i] && map[x][i] < inf) dfs(i); } int cnt; void work() { cnt = 0; Arrays.fill(vis, false); dfs(1); if (cnt < n) { System.out.println("poor snoopy"); return; } Arrays.fill(vis, false); while (true) { build(); if (!bcc()) break; } for (int i = 2; i <= n; i++) if (!vis[i]) ans += map[pre[i]][i]; System.out.println(String.format("%.2f", ans)); } void init() throws IOException { ans = 0; for (int i = 1; i <= n; i++) { Arrays.fill(map[i], inf); pp[i] = new point(); pp[i].x = next(); pp[i].y = next(); } int a, b; while (m-- > 0) { a = next(); b = next(); map[a][b] = Math.min(map[a][b], pp[b].dis(pp[a])); } } point pp[] = new point[110]; int n, m; double ans; StreamTokenizer in = new StreamTokenizer(new BufferedReader( new InputStreamReader(System.in))); int next() throws IOException{ in.nextToken(); return (int)in.nval; } void run() throws IOException { while (in.nextToken()!=in.TT_EOF) { n = (int)in.nval; m =next(); init(); work(); } } public static void main(String[] args) throws IOException { new Command3164().run(); } }