海平面上升:http://www.fjutacm.com/Contest.jsp?cid=875#P0
fold的毒瘤题(easy): http://www.fjutacm.com/Contest.jsp?cid=869#P8
这两道题既有相似的地方,又有不同的地方。总之,就是方法多了有点乱,特意研究一下。
一,海平面上升
{ floyd 直接枚举所有路径的情况,然后可以直接查询 能满足两点连接的最高高度
{ kruskal 用生成树的方法 直接连接所有满足 条件的边,然后看一下所求的两个点是否连接
二,fold的毒瘤题
{ floyd 直接枚举所有路径的情况,然后求出 特殊点间的最大路径
{ kruskal 因为题目保证特殊点之间一定可以连接,所以不用判断这些点是否连通。但是,题目要求我们记录边权最大值最小的哪个方案,可以注意到,我们使用 kruskal 的过程中,选的边的权值是递增的,所以当我们在这些特殊 点连通之后,立刻退出,则此刻连通这些特殊点的边 即为边权最大值最小的那个方案,且添加的最后一条边满足:①必在特殊点的连通路径上 ② 连接边上最大的一条边,所以这条边就是答案。
{ prim 严格来说原理和 kruskal 一样,只是实现方法不一样,所以这里基本和上面一样,只是 退出条件变了,
上面是在这些特殊 点连通之后,prim 是这些特殊点全部并入 生成树的点集之时。
三,思考
1,两道题目都可以看成求同一个东西。第一题是:求边权最小值最大的那条边,第二题是:求边权最大值最小的那条边
因为只记录一个数据,且限制条件都是和边有关的,所以都可以直接用 floyd 暴力任意两点间的所有路的情况,并记录数据。
2,为什么 第二题可以用 prim,第二题 可以呢?
注意 kruskal 和 prim 在实现方法的不同,一个是依赖于边,一个是依赖于点。
上这两题都给出了边的限制条件,但第一题却没有给出点的,所以不建议用 prim
这里说明一下,第一题 边的限制条件:桥的高度比海平面高,才能通过。而点的限制条件,需要我们先求出 满足条件的边,在求出这些边的端点,十分麻烦。
而第二题 没有给出边的限制条件,却给出了点的限制条件,但 kruskal 的并查集也是可以用点进行判断的,所以两种方法都可以。
3,第二题只要求我们连接这些特殊点,但是我们却直接用 最小生成树的方法,不会有影响吗?
不错,第二题我们在最后停止后,生成的树,是会出现割边的 ,且那个度数为 1 的点不是特殊点,这些边没有贡献于特殊点的连接。
其实这个可以用反证法,①,这些无用割边绝不会是连通的最后一条边,所以不会被选中
②,我们要求的不是 连接所有特殊点的所有路径,而是 求边权最大值最小的那个方案,只要连接的每一条边都是最小的就好,且你是刚刚好连通就退出。
假设还有一个方案的边权最大值比我们求出来的还小,则这样你的其他方案,必然至少要添加此刻这颗不完全的生成树没有的边,
而这条边必然比这里所有的边都大,也就无法比我们求出来的还小了,这就矛盾了。
四,代码
第一题 floyd
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX(x,y) ((x)>(y))?(x):(y) #define MIN(x,y) ((x)<(y))?(x):(y) #define N 205 #define inf 0x3f3f3f3f // 10 位数 int dp[N][N]; // 刚开始存的是边的距离,到后面就是 i 到 j 的所有路中,恰好能保证通过,的桥的最低距离,即当条路最低,所有路最高 的桥的高度 int n, m, q; void init() { memset(dp, 0, sizeof(dp)); // 0 表示 i 到 j 没有直达路径 // 因为多条路径 要求最大值,所以要初始化 为 0,可以保证无路的这条边 不会被选中 } void floyd() { for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { // if (dp[i][k] && dp[k][j]) // 列举 i 到 j 的某条路径 dp[i][j] = MAX(dp[i][j], MIN(dp[i][k], dp[k][j])); // MIN(dp[i][k], dp[k][j]) 同一条路径中 其决定性的是最短的 短板原理 // MAX(dp[i][j], MIN(dp[i][k], dp[k][j])) 在所有路径中决定可达能否的因素是:最高的桥的高度 } } } } int main(void) { init(); scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= m; i++) { int a, b, h; scanf("%d%d%d", &a, &b, &h); dp[a][b] = dp[b][a] = MAX(dp[a][b], h); } floyd(); while (q--) { int x, y, h; scanf("%d%d%d", &x, &y, &h); if (dp[x][y]>h) puts("G0"); else puts("PlTY"); } system("pause"); return 0; }
第一题 kruskal
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<algorithm> #define MAX(x,y) (x>y?x:y) using namespace std; #define N 20005 struct edge { int from, to, w; }e[N]; int cmp(edge& a, edge& b) { return a.w > b.w; // 要先选桥高的桥,这样才能尽可能 通过 } int p[N], n, m, q; int find(int x) { if (x != p[x]) p[x] = find(p[x]); return p[x]; } int join(int x, int y) { x = find(x), y = find(y); if (x == y) return 0; p[x] = y; return 1; } void init() { for (int i = 0; i <= n; i++) p[i]=i; } int main(void) { scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].w); sort(e + 1, e + m + 1, cmp); while (q--) { init(); int x, y, h; scanf("%d%d%d", &x, &y, &h); int sum = 0, f = 1; for (int i = 1; i <= m&&e[i].w>h; i++) { join(e[i].from, e[i].to); // 将所有高于 海平的桥 连接在一起,看一下 是否能让 x,y 连通 if (find(x) == find(y)) { f = 0; break; } } if (f == 0) puts("G0"); else puts("PlTY"); } system("pause"); return 0; }
第二题 floyd
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<algorithm> #include<string.h> #define MAX(x,y) (x>y?x:y) #define MIN(x,y) (x<y?x:y) #define inf 0x3f3f3f3f #define N 505 int dp[N][N], n, m, q; int a[N]; // 存特殊点的编号的 using namespace std; void init() { memset(dp, 0x3f, sizeof(dp)); // 因为保证 任意两点 之间 必有路,且最后 找的是最大值中的最小值,初始化为 inf,才不会被选中 } void folyd() { for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { //if (dp[i][k] && dp[k][j]) 因为保证 任意两点 之间 必有路,所以不用判断 dp[i][j] = MIN(dp[i][j], MAX(dp[i][k], dp[k][j])); // MAX(dp[i][k], dp[k][j]) 单条路径 找最大值 // MIN(dp[i][j], MAX(dp[i][k], dp[k][j])) 多条路径 找最大值的最小值 } } } } int main(void) { scanf("%d%d%d", &n, &m, &q); init(); for (int i = 0; i < m; i++) { int a, b, w; scanf("%d%d%d", &a, &b, &w); dp[a][b] = dp[b][a] = MIN(dp[a][b], w); // 让同一条路径上的 边权最大值 尽可能小 } folyd(); while (q--) { int L, R, P, C; scanf("%d%d%d%d", &L, &R, &P, &C); int sum = 0, tot = -1; for (int i = L; i <= R; i++) if (i%P == C) a[++tot] = i; for (int i = 0; i <= tot; i++) // 找到这些特殊点中的 边权最大值 { for (int j = i + 1; j <= tot; j++) { sum = MAX(dp[a[i]][a[j]], sum); } } printf("%d ", sum); } system("pause"); return 0; }
第二题 kruskal
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<algorithm> using namespace std; #define MAX(x,y) (x>y)?(x):(y) #define N 505 struct edge { int from, to, w; }e[N]; int cmp(edge& a, edge& b) { return a.w < b.w; // 单条路每一条边尽可能小,才能让 这条路的最大值 最小 } int p[N], n, m, q; int a[N]; int find(int x) { if (x != p[x]) p[x] = find(p[x]); return p[x]; } int join(int x, int y) { x = find(x), y = find(y); if (x == y) return 0; p[x] = y; return 1; } void init() { for (int i = 1; i <= n; i++) p[i] = i, a[i] = 0; } int main(void) { scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].w); sort(e + 1, e + m + 1, cmp); while (q--) { init(); int L, R, P, C; scanf("%d%d%d%d", &L, &R, &P, &C); int sum = 0, tot = -1; for (int i = L; i <= R; i++) if (i%P == C) a[++tot] = i; for (int i = 1; i <= m; i++) { join(e[i].from, e[i].to); sum = e[i].w; // 都选的边一定是生成子图中最大的,所以停止的时候, sum 的值一定是在 子图的生成树的路径上 int f = 0; for (int j = 0; j < tot; j++) if (find(a[j]) != find(a[j + 1])) f = 1; if (f == 0) break; } printf("%d ", sum); } system("pause"); return 0; }
第二题 prim
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX(x,y) (x>y?x:y) #define MIN(x,y) (x<y?x:y) #define N 55 #define inf 0x3f3f3f3f-1 int a[N][N], vis[N], dis[N], sc[N]; int n, m, q; int sum, s; void prim() { memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; i++) dis[i] = inf; vis[s] = 1, sc[s] = 0; int ans = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (vis[j] == 0 && dis[j] > a[s][j]) dis[j] = a[s][j]; } int min = inf; for (int j = 1; j <= n; j++) { if (vis[j] == 0&&dis[j] < min) { min = dis[j]; s = j; } } if (min == inf) return; vis[s] = 1; if (sc[s]) sum--, sc[s] = 0; ans = MAX(ans, min); if (sum==1) break; } printf("%d ", ans); } int main(void) { scanf("%d%d%d", &n, &m, &q); memset(a, 0x3f, sizeof(a)); for (int i = 1; i <= m; i++) { int u, v, w; scanf("%d%d%d", &u,&v,&w); a[v][u] = a[u][v] = MIN(a[u][v], w); } while (q--) { memset(sc, 0, sizeof(sc)); int L, R, P, C; scanf("%d%d%d%d", &L, &R, &C, &P); sum = 0; for (int i = L; i <= R; i++) { if (i%C == P) { s = i; // 起点要在特殊点 sc[i] = 1; sum++; // 点数 } } prim(); } system("pause"); return 0; }
============ ========== ======== ======= ======= ===== ==== === == =
葬花吟 (曹雪芹)林黛玉 清
天尽头,何处有香丘?
未若锦囊收艳骨,一抔净土掩风流。
质本洁来还洁去,强于污淖陷渠沟。
尔今死去侬收葬,未卜侬身何日丧?
侬今葬花人笑痴,他年葬侬知是谁?
试看春残花渐落,便是红颜老死时。
一朝春尽红颜老,花落人亡两不知!