HDU 4081 Qin Shi Huang's National Road System prim + (DP 或 树形DP) 好题
题意:
有n个(n<=1000)城市,告诉坐标(int),边的权值就是两点的距离,并且每个城市都有人居住,现在要修路n-1条路,使得每个城市都连通。现能让一条边可以不用任何花费。求 这条边的两端点的总人数/(包含这条边的最小生成树的总权值-这条边的权值)最大值。即(Wa+Wb)/(mst-w(a,b))最大。
思路:
先求该图的最小生成树,prim,O(n^2);
方法一: 枚举边
枚举最小生成树的每条边,去掉这条边,最小生成树变成了2个各自连通的树,假设为树A,B。分别找到树A,B中人口最多的两个点,这两个点连起来就是去掉这条边所取得的最大比例。用树形DP可以求这个最大比例。
方法二: 枚举点
在求最小生成树的过程中,预处理求出点i到点j之间在最小生成树里的最大边,用dp[i][j] 保存。枚举每两个点,求出最大值。
方法一:
处理1:
1. 用dp[i][j]表示树A中的点i 到 树B及 其子树(j点所在的树)的最大人口,这个过程可以在一边dfs就可以出来,对于每个 i 的dfs 复杂度是O(n) ,外加一个n的循环求出每个点,这里的总复杂度为 O(n^2)。
2. 通过求出来的dp[i][j] 再用一个dfs 求出 树B 到 树A的最大人口,(方法:枚举树A中的所有点 到 树B的最大人口,取其中的最小值。)显然, 这个求出来的值是我们要的去掉这条边所取得的最大比例。
代码1:
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define maxn 1003 #define inf 1000000000 int x[maxn], y[maxn], p[maxn]; double map[maxn][maxn]; double dis[maxn]; int pre[maxn]; bool vis[maxn]; double mst; int minz(int a, int b) { return a < b ? a : b; } int maxz(int a, int b) { return a > b ? a : b; } int n, m; double ans; struct E { int v, next; }edge[maxn<<1]; int tot, head[maxn]; void init() { tot = 0; memset(head, -1, sizeof(int)*(n+1)); } void add(int s, int t) { edge[tot].v = t; edge[tot].next = head[s]; head[s] = tot++; } double Dis(int x1, int y1, int x2, int y2) { return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0); } void prim() { int i, j, k; for(i = 1; i < n; i++) dis[i] = map[0][i], vis[i] = 0, pre[i] = 0; dis[0] = inf, vis[0] = 1, pre[0] = -1; mst = 0; for(i = 0; i < n-1; i++) { k = 0; for(j = 1; j < n; j++) if(!vis[j] && dis[k] > dis[j]) k = j; vis[k] = 1; mst += dis[k]; if(pre[k] != -1) { add(pre[k], k); add(k, pre[k]); } for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j]) dis[j] = map[k][j], pre[j] = k; } } int k; int dp[maxn][maxn]; int dfs1(int u, int fa, int rt) // 求 点rt 到 以u为根的数及其子树的最大人口 { int i; for(i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v == fa) continue; dp[rt][u] = maxz(dp[rt][u], dfs1(v, u, rt)); } dp[rt][u] = maxz(dp[rt][u], p[u] + p[rt]); return dp[rt][u]; } int dfs2(int u, int fa, int rt) // 求 以rt为根的数及其子树 到 以u为根的数及其子树的最大人口 { int i, ans = dp[u][rt]; for(i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v == fa) continue; ans = maxz(ans, dfs2(v, u, rt)); } return ans; } void solve() { int i, j; memset(dp, 0, sizeof(dp)); for(i = 0; i < n; i++) dfs1(i, -1, i); double ans = 0; for(i = 0; i < n ;i++) //枚举删去的每条边,求出删去这边所得的最大比例。 for(j = head[i]; j != -1; j = edge[j].next) { int v = edge[j].v; double tmp = dfs2(v, i, i) * 1.0/ (mst - map[i][v]); if(tmp > ans) ans = tmp; } printf("%.2f\n", ans); } int main() { int i, j, cas; scanf("%d", &cas); while(cas--) { scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d%d%d", &x[i], &y[i], &p[i]); init(); for(i = 0; i < n; i++) for(j = 0; j < n; j++) if(i != j) map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]); prim(); solve(); } return 0; }
处理2:
处理1的优化。
用dp[i][j]表示树A(i点所在的树) 到 树B(j点所在的树)的最大人口。
dfs 的功能是 求树A中的点i 到 树B(j点所在的树)的最大人口,跟上面一种方法一样,对于每个 i 的dfs 复杂度是O(n) ,外加一个n的循环求出每个点,这里的总复杂度为 O(n^2)。 关键是, 一边进行dfs,一边通过每一层的dfs返回值来更新dp[i][j]。
代码2:
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define maxn 1003 #define inf 1000000000 int x[maxn], y[maxn], p[maxn]; double map[maxn][maxn]; double dis[maxn]; int pre[maxn]; bool vis[maxn]; double mst; int maxz(int a, int b) { return a > b ? a : b; } int n, m; double ans; struct E { int v, next; }edge[maxn<<1]; int tot, head[maxn]; void init() { tot = 0; memset(head, -1, sizeof(int)*(n+1)); } void add(int s, int t) { edge[tot].v = t; edge[tot].next = head[s]; head[s] = tot++; } double Dis(int x1, int y1, int x2, int y2) { return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0); } void prim() { int i, j, k; for(i = 1; i < n; i++) dis[i] = map[0][i], vis[i] = 0, pre[i] = 0; dis[0] = inf, vis[0] = 1, pre[0] = -1; mst = 0; for(i = 0; i < n-1; i++) { k = 0; for(j = 1; j < n; j++) if(!vis[j] && dis[k] > dis[j]) k = j; vis[k] = 1; mst += dis[k]; if(pre[k] != -1) { add(k, pre[k]); add(pre[k], k); } for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j]) dis[j] = map[k][j], pre[j] = k; } } int dp[maxn][maxn]; int dfs(int u, int fa, int rt) //dfs求rt点 到 以u为根的树及其子树的最大人口 { int i, ans = 0; for(i = head[u] ; i != -1; i = edge[i].next) { int v = edge[i].v; if(v == fa) continue; int tt = dfs(v, u, rt); ans = maxz(ans, tt); dp[u][v] = dp[v][u] = maxz(dp[u][v], tt); //通过dfs的返回值来更新dp[i][j],怎么更新自己理解吧。 } ans = maxz(ans, p[u] + p[rt]); return ans; } void solve() { int i, j; memset(dp, 0, sizeof(dp)); for(i = 0; i < n; i++) dfs(i, -1, i); double ans = 0; for(i = 0; i < n; i++) for(j = head[i]; j != -1; j = edge[j].next) { int v = edge[j].v; double tmp = dp[v][i] * 1.0/(mst - map[v][i]); if(tmp > ans) ans = tmp; } printf("%.2f\n", ans); } int main() { int i, j, cas; scanf("%d", &cas); while(cas--) { scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d%d%d", &x[i], &y[i], &p[i]); init(); for(i = 0; i < n; i++) for(j = 0; j < n; j++) if(i != j) map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]); prim(); solve(); } return 0; }
处理3:
处理1,2的优化。
删去一边时,我们在分别找树A和树B中的人口最大时,发现人口最大的一个城市一定会被选上,这一点不难证明,那么我们就找出这个城市,以这个城市为根节点,我们只需一个dfs来求删去每条生成树的边所取得的最大比例。
代码3:
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define maxn 1003 #define inf 1000000000 int x[maxn], y[maxn], p[maxn]; double map[maxn][maxn]; double dis[maxn]; int pre[maxn]; bool vis[maxn]; double mst; int maxz(int a, int b) { return a > b ? a : b; } int n, m; double ans; struct E { int v, next; }edge[maxn<<1]; int tot, head[maxn]; void init() { tot = 0; memset(head, -1, sizeof(int)*(n+1)); } void add(int s, int t) { edge[tot].v = t; edge[tot].next = head[s]; head[s] = tot++; } double Dis(int x1, int y1, int x2, int y2) { return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0); } void prim() { int i, j, k; for(i = 1; i < n; i++) dis[i] = map[0][i], vis[i] = 0, pre[i] = 0; dis[0] = inf, vis[0] = 1, pre[0] = -1; mst = 0; for(i = 0; i < n-1; i++) { k = 0; for(j = 1; j < n; j++) if(!vis[j] && dis[k] > dis[j]) k = j; vis[k] = 1; mst += dis[k]; if(pre[k] != -1) { add(k, pre[k]); add(pre[k], k); } for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j]) dis[j] = map[k][j], pre[j] = k; } } int k; int dfs(int u, int fa) { int i, tmp = p[u]; for(i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v == fa) continue; int tt = dfs(v, u); double pp = (p[k] + tt) / (mst - map[u][v]); if(ans < pp) ans = pp; tmp = maxz(tmp, tt); } return tmp; } void solve() { int i, j; ans = 0; k = 0; for(i = 1; i < n; i++) if(p[i] > p[k]) k = i; dfs(k, -1); printf("%.2f\n", ans); } int main() { int i, j, cas; scanf("%d", &cas); while(cas--) { scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d%d%d", &x[i], &y[i], &p[i]); init(); for(i = 0; i < n; i++) for(j = 0; j < n; j++) if(i != j) map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]); prim(); solve(); } return 0; }
方法二:
代码4:
View Code
#include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> using namespace std; #define maxn 1003 #define inf 1000000000 int x[maxn], y[maxn], p[maxn]; double map[maxn][maxn]; double dis[maxn]; int pre[maxn]; bool vis[maxn]; double mst; double dp[maxn][maxn]; double maxz(double a, double b) { return a > b ? a : b; } int n, m; double ans; double Dis(int x1, int y1, int x2, int y2) { return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0); } void prim() { int i, j, k; memset(dp, 0, sizeof(dp)); for(i = 1; i < n; i++) dis[i] = map[0][i], vis[i] = 0, pre[i] = 0; dis[0] = inf, vis[0] = 1, pre[0] = -1; mst = 0; for(i = 0; i < n-1; i++) { k = 0; for(j = 1; j < n; j++) if(!vis[j] && dis[k] > dis[j]) k = j; vis[k] = 1; mst += dis[k]; dp[pre[k]][k] = dp[k][pre[k]] = map[k][pre[k]]; for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j]) dis[j] = map[k][j], pre[j] = k; for(j = 1; j < n; j++) if(vis[j] && j != k) dp[j][k] = dp[k][j] = maxz(dp[j][pre[k]], dp[pre[k]][k]); } } void solve() { int i, j; double ans = 0; for(i = 0; i < n ;i++) for(j = 0; j < n; j++) { if(i == j) continue; double tmp = (p[i] + p[j]) * 1.0/ (mst - dp[i][j]); if(tmp > ans) ans = tmp; } printf("%.2f\n", ans); } int main() { int i, j, cas; scanf("%d", &cas); while(cas--) { scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d%d%d", &x[i], &y[i], &p[i]); for(i = 0; i < n; i++) for(j = 0; j < n; j++) if(i != j) map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]); prim(); solve(); } return 0; }


浙公网安备 33010602011771号