[kuangbin带你飞]专题八 生成树 题解


A - The Unique MST (POJ - 1679)

题意: 问最小生成树是不是唯一的,如果是则输出边权和,如果不是输出Not Unique!

思路: 首先生成一个最小生成树,然后每次删掉其中的一条边,再去生成最小生成树,如果能生成权值和与之前一样的,那说明最小生成树不唯一,否则唯一。

AC Code:

#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<queue>
using namespace std;
const int maxn = 110;
int t, n, m, ans, f[maxn], path[maxn];
struct Edge{
	int u, v, w;
	bool operator < (const Edge &r) const {
		return w < r.w;
	}
}E[maxn * maxn];
int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}
void init() {
	for(int i = 1; i <= n; i++)
		f[i] = i;
}
void Kruskal(int n, int m) {
	int num = 0;
	int result = 0;
	sort(E + 1, E + 1 + m);
	init();
	for(int i = 1; i <= m; i++) {
		int f1 = find(E[i].u);
		int f2 = find(E[i].v);
		if(f1 != f2) {
			path[++num] = i;
			result += E[i].w;
			f[f1] = f2;
		}
		if(num == n - 1)
			break;
	}
	int flag = 0;
	for(int i = 1; i <= num; i++) {
		int sum = 0;
		int ans = 0;
		init();
		for(int j = 1; j <= m; j++) {
			if(j == path[i])
				continue;
			int f1 = find(E[j].u);
			int f2 = find(E[j].v);
			if(f1 != f2) {
				ans++;
				sum += E[j].w;
				f[f1] = f2;
			}
			if(ans == n - 1)
				break;
		}
		if(ans == n - 1 && sum == result){
			flag = 1;
			break;
		}
	}
	if(flag)
		printf("Not Unique!\n");
	else
		printf("%d\n", result);
}
int main() {
	scanf("%d", &t);
	while(t--) {
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= m; i++) {
			scanf("%d %d %d", &E[i].u, &E[i].v, &E[i].w);
		}
		Kruskal(n, m);
	}
	system("pause");
	return 0;
}

B - Qin Shi Huang’s National Road System (HDU - 4081)

题意: 给出 n n n个城市的 x , y x,y x,y坐标以及每个城市的人数, 这些城市的主人想建造最小生成树,这时候有个会魔法的道士说, 我可以让一条路权值为 0 0 0, 求 A / B A/B A/B的最大值, 其中 A A A是权值为0的道路连接的城市的人数之和, B B B是最小生成树的权值。

思路: 先不考虑没有魔法道路,就是生成一棵MST,然后考虑魔法道路。要保证 A / B A/B A/B最大,那就要使 B B B尽量小的情况下保证 A A A尽量大,这条魔道路肯定取代的是最小生成树里的某条路。然后枚举这棵MST中的所有边并删除它们,删除掉MST中的一条边,那么这棵MST就被分成了两个连通块,然后DFS去查找这两块连通块中人口最多的两个城市,用这个魔法道路去连接它们。所有枚举的结果取个最大值就是答案了。

AC Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
#define inf 0x3f3f3f3f
struct Edge{
	int u, v;
	double w;
	bool operator < (const Edge &r)const{
		return w < r.w;
	}
}E[maxn * maxn];
struct Node{
	int to;
	double w;
	Node(int _to = 0, double _w = 0) {
		to = _to;
		w = _w;
	}
};
int t, n, f[maxn], path[maxn];
double maxnum;
vector<Node>G[maxn];
struct Point{
	double x, y, num;
}P[maxn];
double Dist(Point a, Point b) {
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}
void init() {
	for(int i = 1; i <= n; i++) {
		f[i] = i;
		G[i].clear();
	}
}
void DFS(int now, int f) {
	for(int i = 0; i < G[now].size(); i++) {
		int v = G[now][i].to;
		if(f != v)
			DFS(v, now);
	}
	if(maxnum < P[now].num)
		maxnum = P[now].num;
}
void Kruskal(int n, int m) {
	int sum = 0;
	double res = 0;
	sort(E + 1, E + 1 + m);
	init();
	for(int i = 1; i <= n - 1; i++)
		path[i] = 0;
	for(int i = 1; i <= m; i++) {
		int f1 = find(E[i].u);
		int f2 = find(E[i].v);
		if(f1 != f2) {
			G[E[i].u].push_back(Node(E[i].v, E[i].w));
			G[E[i].v].push_back(Node(E[i].u, E[i].w));
			f[f1] = f2;
			path[++sum] = i;
			res += E[i].w;
		}
		if(sum == n - 1) {
			break;
		}
	}
	double ans = -inf;
	for(int i = 1; i <= sum; i++) {
		double A = 0, B = 0;
		int u = E[path[i]].u;
		int v = E[path[i]].v;
		maxnum = 0;
		DFS(u, v);
		A += maxnum;
		maxnum = 0;
		DFS(v, u);
		A += maxnum;
		B = res - E[path[i]].w;
		ans = max(ans, A / B); 
	}
	printf("%.2f\n", ans);
}
void solve() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf("%lf %lf %lf", &P[i].x, &P[i].y, &P[i].num);
	int m = 0;
	for(int i = 1; i < n; i++) {
		for(int j = i + 1; j <= n; j++) {
			E[++m].u = i;
			E[m].v = j;
			E[m].w = Dist(P[i], P[j]);
		}
	}
	Kruskal(n, m);	
}
int main() {
	scanf("%d", &t);
	while(t--)
		solve();
	system("pause");
	return 0;
}

C - ACM Contest and Blackout (UVA - 10600)

思路: 就是求最小生成树权值和次小生成树权值。值得注意的点就是最后求次小生成树权值的时候要先判断边数有没有到达 n − 1 n - 1 n1条,刚开始我由于没有判断这个 W A WA WA了几发。

AC Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
#define inf 0x3f3f3f3f
struct Edge{
	int u, v, w;
	bool operator < (const Edge &r)const{
		return w < r.w;
	}
}E[maxn * maxn];
int t, n, m, f[maxn], path[maxn];
int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}
void init() {
	for(int i = 1; i <= n; i++)
		f[i] = i;
}
void Kruskal(int n, int m) {
	init();
	for(int i = 1; i < n; i++)
		path[i] = 0;
	int num = 0, res = 0;
	sort(E + 1, E + 1 + m);
	for(int i = 1; i <= m; i++) {
		int f1 = find(E[i].u);
		int f2 = find(E[i].v);
		if(f1 != f2) {
			path[++num] = i;
			f[f1] = f2;
			res += E[i].w;
		}
		if(num == n - 1)
			break;
	}
	int minn = inf;
	for(int i = 1; i <= num; i++) {
		int ans = 0, sum = 0;
		init();
		for(int j = 1; j <= m; j++) {
			if(j == path[i]) continue;
			int f1 = find(E[j].u);
			int f2 = find(E[j].v);
			if(f1 != f2) {
				ans++;
				f[f1] = f2;
				sum += E[j].w;
			}
			if(ans == n - 1)
				break;
		}
		if(ans == n - 1)
			minn = min(minn, sum);
	}
	printf("%d %d\n", res, minn);
}
void solve() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= m; i++) 
		scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].w);
	Kruskal(n, m);
}
int main() {
	scanf("%d", &t);
	while(t--)
		solve();
	system("pause");
	return 0;
}

D - Is There A Second Way Left? (UVA - 10462)

思路: 三种情况,判断最小生成树是否存在,判断次小生成树是否存在,输出次小生成树的权值。

AC Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
#define inf 0x3f3f3f3f
struct Edge{
	int u, v, w;
	bool operator < (const Edge &r)const{
		return w < r.w;
	}
}E[maxn * 2];
int t, n, m, f[maxn], path[maxn];
int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}
void init() {
	for(int i = 1; i <= n; i++)
		f[i] = i;
}
void Kruskal(int n, int m, int cas) {
	init();
	for(int i = 1; i < n; i++)
		path[i] = 0;
	int num = 0, res = 0;
	int flag = 0;
	printf("Case #%d : ", cas);
	sort(E + 1, E + 1 + m);
	for(int i = 1; i <= m; i++) {
		int f1 = find(E[i].u);
		int f2 = find(E[i].v);
		if(f1 != f2) {
			path[++num] = i;
			f[f1] = f2;
			res += E[i].w;
		}
		if(num == n - 1)
			break;
	}
	if(num != n - 1) {
		puts("No way");
		return ;
	}
	int minn = inf;
	for(int i = 1; i <= num; i++) {
		int ans = 0, sum = 0;
		init();
		for(int j = 1; j <= m; j++) {
			if(j == path[i]) continue;
			int f1 = find(E[j].u);
			int f2 = find(E[j].v);
			if(f1 != f2) {
				ans++;
				f[f1] = f2;
				sum += E[j].w;
			}
			if(ans == n - 1)
				break;
		}
		if(ans == n - 1) {
			flag = 1;
			minn = min(minn, sum);
		}
	}
	if(flag)
		printf("%d\n", minn);
	else 
		puts("No second way");
}
void solve(int cas) {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= m; i++) 
		scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].w);
	Kruskal(n, m, cas);
}
int main() {
	scanf("%d", &t);
	for(int cas = 1; cas <= t; cas++)
		solve(cas);
	system("pause");
	return 0;
}

E - Command Network(POJ - 3164)

思路: 最小树形图模板题(就是有向边的最小生成树),找了个不错的模板。

AC Code:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double inf = 2e9;
const int maxn = 105;
const int maxm = 10050;
int n, m;//顶点数,边数 
double x[maxn], y[maxn];//坐标 
double in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn];//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 
struct Edge {
    int from, to;
    double dist;
    Edge(int f = 0, int t = 0, double d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 
double dis(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}
double direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    double ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
            }
        }
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }
        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }
        if (0 == cnt) break;//没有自环说明已经求出最终结果
        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  
        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}
int main() {
    while (scanf("%d%d", &n, &m) == 2) {
        for (int i = 0; i < n; ++i) scanf("%lf%lf", &x[i], &y[i]);
        for (int i = 0; i < m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            --u, --v;
            if (u == v) {//去除自环,权值设为无穷大 
                edges[i] = Edge(u, v, inf);
                continue;
            }
            double d = dis(x[u], y[u], x[v], y[v]);
            edges[i] = Edge(u, v, d);
        }
        double ans = direct_mst(0, n, m);
        if (ans == -1) printf("poor snoopy\n");
        else printf("%.2f\n", ans);
    }
    return 0;
}

F - Teen Girl Squad (UVA - 11183)

思路: 最小树形图模板题

AC Code:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double inf = 2e9;
const int maxn = 1010;
const int maxm = 40050;
int n, m, t;//顶点数,边数 
int in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn];//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 
struct Edge {
    int from, to, dist;
    Edge(int f = 0, int t = 0, int d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 
int direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    int ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
            }
        }
		// for(int i = 0; i < V; i++)
		// 	printf("*%d\n", in[i]);
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }
        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }
        if (0 == cnt) break;//没有自环说明已经求出最终结果
        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  
        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}
int main() {
	scanf("%d", &t);
	for(int cas = 1; cas <= t; cas++) {
		scanf("%d %d", &n, &m);
		for (int i = 0; i < m; ++i) {
			int u, v, d;
			scanf("%d%d%d", &u, &v, &d);
			// --u, --v;
			// if (u == v) {//去除自环,权值设为无穷大 
			// 	edges[i] = Edge(u, v, inf);
			// 	continue;
			// }
			edges[i] = Edge(u, v, d);
		}
		int ans = direct_mst(0, n, m);
		printf("Case #%d: ", cas);
		if (ans == -1) printf("Possums!\n");
		else printf("%d\n", ans);
	}
	system("pause");
    return 0;
}

G - Ice_cream’s world II (HDU - 2121)

思路: 这题不定根,如果对每一个根都遍历一遍肯定TLE,去学了一下dalao们虚根的写法,太妙了,但最小树形图还是只会套板子。

AC Code:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double inf = 2e9;
const int maxn = 1010;
const int maxm = 10050;
int n, m, t;//顶点数,边数 
int in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn], pos;//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 
struct Edge {
    int from, to, dist;
    Edge(int f = 0, int t = 0, int d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 
int direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    int ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
				if(u == root)
					pos = i;     //真正的起点
            }
        }
		// for(int i = 0; i < V; i++)
		// 	printf("*%d\n", in[i]);
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }
        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }
        if (0 == cnt) break;//没有自环说明已经求出最终结果
        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  
        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}
int main() {
	while(~scanf("%d %d", &n, &m)){
		int sum = 0;
		for (int i = 0; i < m; ++i) {
			int u, v, d;
			scanf("%d%d%d", &u, &v, &d);
			// --u, --v;
			// if (u == v) {//去除自环,权值设为无穷大 
			// 	edges[i] = Edge(u, v, inf);
			// 	continue;
			// }
			++u, ++v;
			sum += d;
			edges[i] = Edge(u, v, d);
		}
		++sum;
		for(int i = m; i < m + n; i++) {
			edges[i] = Edge(0, i - m + 1, sum);
		}
		int ans = direct_mst(0, n + 1, m + n);
		if(ans == -1 || ans - sum >= sum)
			puts("impossible");
		else 
			printf("%d %d\n", ans - sum, pos - m);
		puts("");
	}
	system("pause");
    return 0;
}

H - Transfer water (HUD - 4009)

思路: 还是最小树形图的模板,建边的时候处理一下就好了。然后可以弄一个虚根,就是每家都自己建一个水井,保证了这题肯定有解。我晕了,这也太难了。

AC Code:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double inf = 2e9;
const int maxn = 1010;
const int maxm = 1000050;
int X, Y, Z;
int n, m, t;//顶点数,边数 
int in[maxn];//in[u]记录当前图中指向u结点的所有边权中最小的那条边权 
int pre[maxn];//pre[u]记录最小边权对应的父亲结点 
int used[maxn], id[maxn], pos;//used是访问标记数组,id[u]是计算出u在下一次的新图中的编号 
struct Edge {
    int from, to, dist;
    Edge(int f = 0, int t = 0, int d = 0) :from(f), to(t), dist(d) {}
}edges[maxm];//边集 
struct Point{
	int x, y, z;
}P[maxn];
int Dist(Point a, Point b) {
	return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z);
}
int direct_mst(int root, int V, int E) {//三个参数分别是根结点,顶点数量,边数量 
    int ans = 0;
    while (1) {
        //为每个非根结点选出最小入边 
        for (int i = 0; i < V; ++i) in[i] = inf;
        for (int i = 0; i < E; ++i) {
            int u = edges[i].from;
            int v = edges[i].to;
            if (in[v] > edges[i].dist && u != v) {
                in[v] = edges[i].dist;
                pre[v] = u;
				if(u == root)
					pos = i;     //真正的起点
            }
        }
		// for(int i = 0; i < V; i++)
		// 	printf("*%d\n", in[i]);
        //判断连通性,如有不可达结点说明无解
        for (int i = 0; i < V; ++i) {
            if (i == root) continue;
            if (inf == in[i]) return -1;
        }
        //判断有向环是否存在,存在有向环就缩圈
        int cnt = 0;//生成新图的结点编号
        memset(id, -1, sizeof(id));//id[u]==-1表示结点u还不属于任何一个自环 
        memset(used, -1, sizeof(used));
        in[root] = 0;
        for (int i = 0; i < V; ++i) {
            ans += in[i];
            int v = i;
            while (used[v] != i && id[v] == -1 && v != root) {//每个结点不断向上寻找父亲结点,要么找到根结点,要么形成一个自环 
                used[v] = i;
                v = pre[v];
            }
            if (v != root && id[v] == -1) {//找到了自环,进行缩点,更新id数组 
                for (int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
                id[v] = cnt++;
            }
        }
        if (0 == cnt) break;//没有自环说明已经求出最终结果
        //建立新图
        for (int i = 0; i < V; i++)
            if (id[i] == -1) id[i] = cnt++;//先把不在自环中的点的编号更新  
        for (int i = 0; i < E; i++) {
            int u = edges[i].from;
            int v = edges[i].to;
            edges[i].from = id[u];
            edges[i].to = id[v];
            if (id[u] != id[v]) edges[i].dist -= in[v];
            //这里id[u] != id[v]说明edges[i]这条边原来不在有向环中,
            //如果这条边指向了有向环,那么它的边权就要减少in[v]等价于整个环的边权减去in[v]
            //而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过
            //程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0 
        }
        V = cnt;
        root = id[root];
    }
    return ans;
}
int main() {
	while(~scanf("%d%d%d%d", &n, &X, &Y, &Z)) {
		if(n == 0 && X == 0 && Y == 0 && Z == 0)
			break;
		for(int i = 0; i < n; i++)
			scanf("%d%d%d", &P[i].x, &P[i].y, &P[i].z);
		int id = 0;
		for(int i = 0, a, b; i < n; i++) {
			scanf("%d", &a);
			while(a--) {
				scanf("%d", &b);
				edges[id].dist = Dist(P[i], P[--b]) * Y;
				if(P[b].z > P[i].z) edges[id].dist += Z;
				edges[id].from = i; edges[id++].to = b;
			}
		}
		for(int i = 0; i < n; i++) {
			edges[id].from = n; edges[id].to = i;
			edges[id++].dist = P[i].z * X;
		}
		printf("%d\n", direct_mst(n, n + 1, id));
	}
	system("pause");
    return 0;
}

I - Organising the Organisation (UVA - 10766)

题意: 给出 n n n个点, m m m条不能相连的边,问生成树的数量。

思路: 采用了基尔霍夫矩阵和矩阵树定理。基尔霍夫矩阵就是度数矩阵减去邻接矩阵。矩阵树定理就是基尔霍夫矩阵的任意 n − 1 n - 1 n1阶行列式就是生成树的数量。

AC Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 60;
int n, m, k;
ll b[maxn][maxn];
bool a[maxn][maxn];
ll det(int n){
    ll ret=1;
    for(int i = 0; i < n; i++){
        if(b[i][i] == 0){
            int j;
            for(j = i + 1; j < n; j++)
                if(b[j][i] != 0)
                    break;
            if(j == n) return -1;
            for(int k = i; k < n; k++)
                swap(b[j][k], b[i][k]);
            ret = -ret;
        }
        for(int j = i + 1; j < n; j++){
            while(b[j][i]){
                ll t = b[i][i] / b[j][i];
                for(int k = i; k < n; k++){
                    b[i][k] -= t * b[j][k];
                    swap(b[i][k], b[j][k]);
                }
                ret = -ret;
            }
        }
        ret *= b[i][i];
    }
    return ret;
}
int main() {
    while(~scanf("%d%d%d", &n, &m, &k)) {
        memset(a, true, sizeof(a));
        memset(b, 0, sizeof(b));
        for(int i = 0, x, y; i < m; i++) {
            scanf("%d%d", &x, &y);
            x--, y--;
            a[x][y] = a[y][x] = false;
        }
        for(int i = 0; i < n - 1; i++) {
            for(int j = i + 1; j < n; j++) {
                if(a[i][j] == true) {
                    b[i][i]++;
                    b[j][j]++;
                    b[i][j]--;
                    b[j][i]--;
                }
            }
        }
        printf("%lld\n", det(n - 1));
    }
    system("pause");
    return 0;
}

J - Find The Determinant III (SPOJ - DETER3)

思路: 求矩阵行列式

AC Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 210;
int n;
ll b[maxn][maxn], mod;
ll det(int n){    //求n阶行列式
	ll ret = 1;
	for(int i = 0; i < n; i++){
		for(int j = i + 1; j < n; j++){
			while(b[j][i]){
				ll t = b[i][i] / b[j][i];
				for(int k = i; k < n; k++){
					b[i][k] = (b[i][k] - t * b[j][k] % mod + mod) % mod;
					swap(b[i][k], b[j][k]);
				}
				ret = (-ret + mod) % mod;
			}
		}
		ret = ret * b[i][i] % mod;
	}
	return ret;
}
int main() {
    while(~scanf("%d%lld", &n, &mod)) {
        memset(b, 0, sizeof(b));
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                scanf("%lld", &b[i][j]);
            }
        }
        printf("%lld\n", det(n));
    }
    system("pause");
    return 0;
}

K - Join (URAL - 1627)

思路: 把相邻的房间看成无向图的一条边,然后建图,然后跑一边生成树计数模板。

AC Code:

#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<queue>
using namespace std;
#define ll long long
const int maxn = 11;
const int mod = 1e9;
int n, m;
char mp[maxn][maxn];
int moven[10][5] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int g[maxn * maxn][maxn * maxn], id[maxn * maxn][maxn * maxn];
ll b[maxn * maxn][maxn * maxn];
bool check(int x, int y) {
    if(x < 0 || x >= n || y < 0 || y >= m || mp[x][y] != '.') return false;
    return true;
}
ll det(int n){    //求n阶行列式
	ll ret = 1;
	for(int i = 0; i < n; i++){
		for(int j = i + 1; j < n; j++){
			while(b[j][i]){
				ll t = b[i][i] / b[j][i];
				for(int k = i; k < n; k++){
					b[i][k] = (b[i][k] - t * b[j][k] % mod + mod) % mod;
					swap(b[i][k], b[j][k]);
				}
				ret = (-ret + mod) % mod;
			}
		}
		ret = ret * b[i][i] % mod;
	}
	return (ret + mod) % mod;
}
int main() {
    scanf("%d%d", &n, &m);
    memset(g, 0, sizeof(g));
    memset(b, 0, sizeof(b));
    getchar();
    for(int i = 0; i < n; i++)
        gets(mp[i]);
    int tot = 0;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            if(mp[i][j] == '*') continue;
            id[i][j] = tot++;
        }
    }
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            if(mp[i][j] == '.') {
                for(int k = 0; k < 4; k++) {
                    int mx = i + moven[k][0];
                    int my = j + moven[k][1];
                    if(check(mx, my)) {
                        g[id[i][j]][id[mx][my]] = g[id[mx][my]][id[i][j]] = 1;
                    }
                }
            }
        }
    }
    for(int i = 0; i < tot - 1; i++) {
        for(int j = i + 1; j < tot; j++) {
            if(g[i][j]) {
                b[i][i]++;
                b[j][j]++;
                b[i][j]--;
                b[j][i]--;
            }
        }
    }
    printf("%lld\n", det(tot - 1));
    system("pause");
    return 0;
}

L - Lightning (HDU - 4305)

题意: 平面上有 n n n个机器人,他们之间相连的条件是距离小于 r r r,并且中间不能有其他机器人。

思路: 暴力建边,枚举每两个点,判断这两个点是否距离满足条件,并且判断这两个点构成的线段上有没有其他机器人,如果这两个条件都满足则建边,然后用矩阵树定理求生成树个数。

AC Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 310;
const int mod = 10007;
const double pi = acos((double)(-1));
#define eps 1e-8
int n, t;
double r;
ll b[maxn][maxn];
int sgn(double x){                 //判断x是否为0
    if(fabs(x) < eps) return 0;
    else return x < 0?-1:1;
}
struct Point{
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}
    Point operator + (Point B){return Point(x + B.x,y + B.y);}
    Point operator - (Point B){return Point(x - B.x,y - B.y);}
    Point operator * (double k){return Point(x*k,y*k);}
    Point operator / (double k){return Point(x/k,y/k);}
    bool operator == (Point B){return sgn(x - B.x) == 0 && sgn(y - B.y) == 0;}
}P[maxn];
typedef Point Vector;
double Dist(Point A,Point B){
    return sqrt((A.x - B.x)*(A.x - B.x) + (A.y - B.y)*(A.y - B.y));
}
struct Line{
    Point p1,p2;
    Line(){};
    Line(Point p1,Point p2):p1(p1),p2(p2){}
    Line(Point p,double angle){    //y = kx + b
        p1 = p;
        if(sgn(angle - pi/2) == 0){p2 = (p1 + Point(0,1));}
        else {p2 = (p1 + Point(1,tan(angle)));}
    }
    Line(double a,double b,double c){    //ax + by + c = 0
        if(sgn(a) == 0){
            p1 = Point(0, -c/b);
            p2 = Point(1, -c/b);
        }
        else if(sgn(b) == 0){
            p1 = Point(-c/a,0);
            p2 = Point(-c/a,1);
        }
        else{
            p1 = Point(0,-c/b);
            p2 = Point(1,(-c - a)/b);
        }
    }
}L[maxn];
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
double Dot(Vector A,Vector B){return A.x*B.x + A.y*B.y;} 
bool Point_on_seg(Point p, Line v){  //0为不在线段上,1为在线段上
    return sgn(Cross(p - v.p1, v.p2 - v.p1)) == 0 && 
    sgn(Dot(p - v.p1, p - v.p2)) <=0;
}
bool check(int i, int j) {
    if(sgn(Dist(P[i], P[j]) - r) > 0) return false;
    Line l = Line(P[i], P[j]);
    for(int k = 0; k < n; k++) {
        if(k == i || k == j) continue;
        if(Point_on_seg(P[k], l) == 1) return false;
    }
    return true;
}
ll det(int n){    //求n阶行列式
	ll ret = 1;
	for(int i = 0; i < n; i++){
		for(int j = i + 1; j < n; j++){
			while(b[j][i]){
				ll t = b[i][i] / b[j][i];
				for(int k = i; k < n; k++){
					b[i][k] = (b[i][k] - t * b[j][k] % mod + mod) % mod;
					swap(b[i][k], b[j][k]);
				}
				ret = (-ret + mod) % mod;
			}
		}
		ret = ret * b[i][i] % mod;
	}
	return (ret + mod) % mod;
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d %lf", &n, &r);
        memset(b, 0, sizeof(b));
        for(int i = 0; i < n; i++)
            scanf("%lf %lf", &P[i].x, &P[i].y);
        for(int i = 0; i < n - 1; i++) {
            for(int j = i + 1; j < n; j++) {
                if(check(i, j)) {
                    b[i][i]++;
                    b[j][j]++;
                    b[i][j]--;
                    b[j][i]--;
                }
            }
        }
        ll ans = det(n - 1);
        if(ans == 0)
            printf("-1\n");
        else
            printf("%lld\n", ans);
    }
    system("pause");
    return 0;
}

M - Minimum Spanning Tree (HDU - 4408)

思路: 最小生成树计数,看了挺久看不懂的,先搞个模板放着好了。

AC Code:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
#define ll long long 
struct edge {
	int u, v, w;
	bool operator<(const edge& temp)const {
		return w < temp.w;
	}
} edges[1005];
ll B[maxn][maxn], G[maxn][maxn];
int n, m, mod;
int pre[maxn], U[maxn];
bool vis[maxn];
vector<int> e[maxn];
ll determina(int n) {
	ll res = 1;
	for (int i = 1; i <= n; i++) {
		if (!B[i][i]) { //若果对角线元素为0,把此行都一都移到下一行去
			bool flag = false;
			for (int j = i + 1; j <= n; j++) { //从i+1行开始找i列中的第一个不为0的元素,与现在的行交换
				if (B[j][i]) {//找到了该列不为0的元素,
					flag = 1; //标记,交换
					for (int k = i; k <= n; k++) swap(B[i][k], B[j][k]);
					res = -res;// 换行系数变为负数
					break; //退出.
				}
			}
			if (!flag) return 0; //这一行全部为0,行列式值为0
		}
		for (int j = i + 1; j <= n; j++) {
			while (B[j][i]) { //从下面的行找一个不为0的元素与第i行进行消元
				ll t = B[i][i] / B[j][i];
				for (int k = i; k <= n; k++) {
					B[i][k] = (B[i][k] - t * B[j][k]) % mod;
					swap(B[i][k], B[j][k]);//消元后,把0的行换到下面来。
				}
				res = -res;
			}
		}
		res *= B[i][i];//对角线元素相乘
		res %= mod;
	}
	return (res + mod) % mod;
}
int find(int x, int* p) {
	if (x == p[x]) return x;
	else return p[x] = find(p[x], p);
}
void kruskal() {
	sort(edges, edges + m);
	for (int i = 1; i <= n; i++) pre[i] = i; memset(vis, 0, sizeof vis);
	ll tempedge = -1;
	ll ans = 1;
	for (int k = 0; k <= m; k++) { //k==m为了最后一步
		if (edges[k].w != tempedge || k == m) { //开启下一阶段之气那处理上一阶段的边
			for (int i = 1; i <= n; i++) {
				if (vis[i]) {
					int u = find(i, U);
					e[u].push_back(i); //记录联通块中所有的顶点
					vis[i] = 0; //取消该点的标记,说明仍未访问到.
				}
			}
			for (int i = 1; i <= n; i++) { //遍历每一个点,找到每一个连通分量
				if (e[i].size() > 1) { //连通分量只有>=3才有意义,2个点只有1种生成树
					memset(B, 0, sizeof B);
					int len = e[i].size(); //除去联通点的顶点的顶点个数,
					for (int a = 0; a < len; a++) //求出基尔霍夫矩阵
						for (int b = a + 1; b < len; b++) {
							int a1 = e[i][a], b1 = e[i][b]; //联通块中的2个不同顶点
							B[a][b] = (B[b][a] -= G[a1][b1]);
							B[a][a] += G[a1][b1];
							B[b][b] += G[a1][b1];
						}
					ll res = determina(len - 1);
					ans = (ans * res) % mod;
					for (int a = 0; a < len; a++) pre[e[i][a]] = i;
				}
			}
			for (int i = 1; i <= n; i++) {
				U[i] = find(i, pre);
				e[i].clear();
			}
			if (k == m) break;
			tempedge = edges[k].w;
		}
		int a = edges[k].u, b = edges[k].v;
		int a1 = find(a, pre), b1 = find(b, pre);
		if (a1 == b1) continue;
		vis[a1] = vis[b1] = 1;
		U[find(a1, U)] = find(b1, U);
		G[a1][b1]++;
		G[b1][a1]++;
	}
	int flag = 0;
	for (int i = 2; i <= n && !flag; i++)
		if (U[i] != U[i - 1]) //仍有多个分块,不能组成生成树
			flag = 1;
	if (m == 0) //边=0
		flag = 1;
	printf("%lld\n", flag ? 0 : ans % mod);
	return;
}
int main() {
	while (scanf("%d%d%d", &n, &m, &mod) && (n + m + mod)) {
		memset(G, 0, sizeof G);
		for (int i = 0; i < m; i++) scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w);
		kruskal();
		for (int i = 1; i <= n; i++) e[i].clear();
	}
	return 0;
}

N - Highways (SPOJ - HIGH)

思路: 裸的生成树计数

AC Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 15;
int t, n, m;
ll b[maxn][maxn];
ll det(int n){    //求n阶行列式
	ll ret = 1;
	for(int i = 0; i < n; i++){
		for(int j = i + 1; j < n; j++){
			while(b[j][i]){
				ll t = b[i][i] / b[j][i];
				for(int k = i; k < n; k++){
					b[i][k] = (b[i][k] - t * b[j][k]);
					swap(b[i][k], b[j][k]);
				}
				ret = -ret;
			}
		}
		ret = ret * b[i][i];
	}
	return (ret);
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d %d", &n, &m);
        memset(b, 0, sizeof(b));
        for(int i = 1, x, y; i <= m; i++) {
            scanf("%d %d", &x, &y);
            x--, y--;
            b[x][x]++;
            b[y][y]++;
            b[x][y]--;
            b[y][x]--;
        }
        printf("%lld\n", det(n - 1));
    }
    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值