HDU 4126 Genghis Khan the Conqueror prim + 树形DP 好题
2015-03-15 22:38
387 查看
转载:/article/7142352.html
题意:
一个N个点的无向图,先生成一棵最小生成树,然后给你Q次询问,每次询问都是x,y,z的形式, 表示的意思是在原图中将x,y之间的边增大(一定是变大的)到z时,此时最小生成数的值是多少。最后求Q次询问最小生成树的平均值。
N<=3000 , Q<=10000
思路:
先求出该图的最小生成树,用prim(), O(n^2)。
对于每次询问, 都是将a,b之间的边增加到c, 会出现 两种情况:
1. 如果边权增加的那条边原先就不在最小生成树中,那么这时候的最小生成树的值不变
2. 如果在原最小生成树中,那么这时候将增加的边从原最小生成树中去掉,这时候生成树就被分成了两个各自联通的部分,可以证明的是,这时候的最小生成树一定是将这两部分联通起来的最小的那条边。
问题转化:
首先我们先求出最小生成树,然后将在最小生成树中边去掉,对于每条最小生成树中的边,我们要求出它的替代边, 并且要求该替代边最小。
对于询问Q(10000) 分析一下Q里面的复杂度必须是O(n) 或 O(1)。
我们需要在外面预处理求出一些跟能推出答案但推出过程的复杂度是以上2个的其中1个。
方法一:
假设两个各自连通的部分分别为树A,树B
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的最近距离,取其中的最小值。)显然, 这个求出来的值是我们要的最小替代边,把它保存到一个best[i][j]数组里面,(best[i][j]表示去掉边<i,j>后它的最小替代边的值)这里的总复杂度为
O(n^2)。
代码:
下面一个代码是跟着别人写的,思路差不多,但很搓,很难解释,它的做法是求出树的前序遍历,然后在数组里面实现树形DP的更新功能,以上的第一步它在询问Q外面做,第二步在询问Q里面做的,我保存其代码的原因是他树形DP的实现很犀利,然后prim用邻接表写的,我这个弱菜不会,很多地方值得学习。
点击打开链接
方法二:
假设两个各自连通的部分分别为树A,树B
用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]。
想法很犀利,代码更很犀利。
具体看代码:
题意:
一个N个点的无向图,先生成一棵最小生成树,然后给你Q次询问,每次询问都是x,y,z的形式, 表示的意思是在原图中将x,y之间的边增大(一定是变大的)到z时,此时最小生成数的值是多少。最后求Q次询问最小生成树的平均值。
N<=3000 , Q<=10000
思路:
先求出该图的最小生成树,用prim(), O(n^2)。
对于每次询问, 都是将a,b之间的边增加到c, 会出现 两种情况:
1. 如果边权增加的那条边原先就不在最小生成树中,那么这时候的最小生成树的值不变
2. 如果在原最小生成树中,那么这时候将增加的边从原最小生成树中去掉,这时候生成树就被分成了两个各自联通的部分,可以证明的是,这时候的最小生成树一定是将这两部分联通起来的最小的那条边。
问题转化:
首先我们先求出最小生成树,然后将在最小生成树中边去掉,对于每条最小生成树中的边,我们要求出它的替代边, 并且要求该替代边最小。
对于询问Q(10000) 分析一下Q里面的复杂度必须是O(n) 或 O(1)。
我们需要在外面预处理求出一些跟能推出答案但推出过程的复杂度是以上2个的其中1个。
方法一:
假设两个各自连通的部分分别为树A,树B
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的最近距离,取其中的最小值。)显然, 这个求出来的值是我们要的最小替代边,把它保存到一个best[i][j]数组里面,(best[i][j]表示去掉边<i,j>后它的最小替代边的值)这里的总复杂度为
O(n^2)。
代码:
#include<stdio.h> #include<string.h> #include<algorithm> #include<vector> using namespace std; const int maxn = 3003; const int maxm = maxn * maxn; const int inf = 1000000000; int n, m; __int64 mst; int map[maxn][maxn]; int dp[maxn][maxn], best[maxn][maxn]; int dis[maxn], pre[maxn]; bool vis[maxn]; vector<int> edge[maxn]; int minz(int a, int b) { return a < b ? a : b; } void init() { int i, j; for(i = 0; i < n; i++) { for(j = 0; j < n; j++) map[i][j] = dp[i][j] = inf; edge[i].clear(); vis[i] = 0; pre[i] = -1; dis[i] = inf; } } void input() { int x, y, z; while(m--) { scanf("%d%d%d", &x, &y, &z); map[x][y] = map[y][x] = z; } } void prim() { int i, j, k; for(i = 1; i < n; i++) { dis[i] = map[0][i]; 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) edge[k].push_back(pre[k]), edge[pre[k]].push_back(k); for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j] ) dis[j] = map[k][j], pre[j] = k; } } int dfs1(int u, int fa, int rt) // 求 点rt 到 以u为根的数及其子树的最小距离 { int i; for(i = 0; i < edge[u].size(); i++) { int v = edge[u][i]; if(v == fa) continue; dp[rt][u] = minz(dp[rt][u], dfs1(v, u, rt)); } if(fa != rt) dp[rt][u] = minz(dp[rt][u], map[rt][u]); return dp[rt][u]; } int dfs2(int u, int fa, int rt) // 求 以rt为根的数及其子树 到 以u为根的数及其子树的最小距离 { int i; int ans = dp[u][rt]; for(i = 0; i < edge[u].size(); i++) { int v = edge[u][i]; if(v == fa) continue; ans = minz(ans, dfs2(v, u, rt)); } return ans; } void solve() { int i,j; for(i = 0; i < n; i++) dfs1(i, -1, i); for(i = 0; i < n; i++) for(j = 0; j < edge[i].size(); j++) { int v = edge[i][j]; best[i][v] = best[v][i] = dfs2(v, i, i); } } void query() { int x, y, z; double sum = 0; scanf("%d", &m); for(int ii = 1; ii <= m; ii++) { scanf("%d%d%d", &x, &y, &z); if(pre[x] != y && pre[y] != x) sum += mst * 1.0; else sum += mst * 1.0 - map[x][y] + minz(best[x][y], z); } printf("%.4f\n", sum/m); } int main() { while( ~scanf("%d%d", &n, &m) && n + m) { init(); input(); prim(); solve(); query(); } return 0; }
下面一个代码是跟着别人写的,思路差不多,但很搓,很难解释,它的做法是求出树的前序遍历,然后在数组里面实现树形DP的更新功能,以上的第一步它在询问Q外面做,第二步在询问Q里面做的,我保存其代码的原因是他树形DP的实现很犀利,然后prim用邻接表写的,我这个弱菜不会,很多地方值得学习。
#include<stdio.h> #include<string.h> #include<algorithm> #include<vector> using namespace std; const int maxn = 3003; const int maxm = maxn * maxn; const int inf = 1000000000; int n, m; __int64 mst; int map[maxn][maxn]; int dp[maxn][maxn], best[maxn][maxn]; int dis[maxn], pre[maxn]; bool vis[maxn]; vector<int> edge[maxn]; int minz(int a, int b) { return a < b ? a : b; } void init() { int i, j; for(i = 0; i < n; i++) { for(j = 0; j < n; j++) map[i][j] = dp[i][j] = inf; edge[i].clear(); vis[i] = 0; pre[i] = -1; dis[i] = inf; } } void input() { int x, y, z; while(m--) { scanf("%d%d%d", &x, &y, &z); map[x][y] = map[y][x] = z; } } void prim() { int i, j, k; for(i = 1; i < n; i++) { dis[i] = map[0][i]; 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) edge[k].push_back(pre[k]), edge[pre[k]].push_back(k); for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j] ) dis[j] = map[k][j], pre[j] = k; } } int dfs1(int u, int fa, int rt) // 求 点rt 到 以u为根的数及其子树的最小距离 { int i; for(i = 0; i < edge[u].size(); i++) { int v = edge[u][i]; if(v == fa) continue; dp[rt][u] = minz(dp[rt][u], dfs1(v, u, rt)); } if(fa != rt) dp[rt][u] = minz(dp[rt][u], map[rt][u]); return dp[rt][u]; } int dfs2(int u, int fa, int rt) // 求 以rt为根的数及其子树 到 以u为根的数及其子树的最小距离 { int i; int ans = dp[u][rt]; for(i = 0; i < edge[u].size(); i++) { int v = edge[u][i]; if(v == fa) continue; ans = minz(ans, dfs2(v, u, rt)); } return ans; } void solve() { int i,j; for(i = 0; i < n; i++) dfs1(i, -1, i); for(i = 0; i < n; i++) for(j = 0; j < edge[i].size(); j++) { int v = edge[i][j]; best[i][v] = best[v][i] = dfs2(v, i, i); } } void query() { int x, y, z; double sum = 0; scanf("%d", &m); for(int ii = 1; ii <= m; ii++) { scanf("%d%d%d", &x, &y, &z); if(pre[x] != y && pre[y] != x) sum += mst * 1.0; else sum += mst * 1.0 - map[x][y] + minz(best[x][y], z); } printf("%.4f\n", sum/m); } int main() { while( ~scanf("%d%d", &n, &m) && n + m) { init(); input(); prim(); solve(); query(); } return 0; }
点击打开链接
方法二:
假设两个各自连通的部分分别为树A,树B
用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]。
想法很犀利,代码更很犀利。
具体看代码:
#include<stdio.h> #include<string.h> #include<algorithm> #include<vector> using namespace std; const int maxn = 3003; const int maxm = maxn * maxn; const int inf = 1000000000; int n, m; __int64 mst; int map[maxn][maxn]; int dp[maxn][maxn]; int dis[maxn], pre[maxn]; bool vis[maxn]; vector<int> edge[maxn]; int minz(int a, int b) { return a < b ? a : b; } void init() { int i, j; for(i = 0; i < n; i++) { for(j = 0; j < n; j++) map[i][j] = dp[i][j] = inf; edge[i].clear(); vis[i] = 0; pre[i] = -1; dis[i] = inf; } } void input() { int x, y, z; while(m--) { scanf("%d%d%d", &x, &y, &z); map[x][y] = map[y][x] = z; } } void prim() { int i, j, k; for(i = 1; i < n; i++) { dis[i] = map[0][i]; 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) edge[k].push_back(pre[k]), edge[pre[k]].push_back(k); for(j = 1; j < n; j++) if(!vis[j] && dis[j] > map[k][j] ) dis[j] = map[k][j], pre[j] = k; } } int dfs(int pos, int u, int fa) //求pos 点 到 以u为根的树及其子树的最小距离 { int i, ans = inf; for(i = 0; i < edge[u].size(); i++) { int v = edge[u][i]; if(v == fa) continue; int tmp = dfs(pos, v, u); ans = minz(ans, tmp); dp[u][v] = dp[v][u] = minz(dp[u][v], tmp); //通过dfs的返回值来更新dp[i][j],怎么更新自己理解吧,我也说不清楚 } if(pos != fa) //保证这条边不是生成树的边, 不然不能更新 ans = minz(ans, map[pos][u]); return ans; } void solve() { int i; for(i = 0; i < n; i++) dfs(i, i, -1); } void query() { int x, y, z; double sum = 0; scanf("%d", &m); for(int ii = 1; ii <= m; ii++) { scanf("%d%d%d", &x, &y, &z); if(pre[x] != y && pre[y] != x) sum += mst * 1.0; else sum += mst * 1.0 - map[x][y] + minz(dp[x][y], z); } printf("%.4f\n", sum/m); } int main() { while( ~scanf("%d%d", &n, &m) && n + m) { init(); input(); prim(); solve(); query(); } return 0; }
相关文章推荐
- HDU 4126 Genghis Khan the Conqueror 最小生成树+树形dp
- HDU 4126 Genghis Khan the Conqueror MST + 树形DP 2011年福州现场赛F题
- HDU 4126 Genghis Khan the Conqueror(树形dp,MST,破坏原有最小生成树边后的最小生成树代价)
- HDU 4126 Genghis Khan the Conqueror 类似于4756的树形DP
- HDU 4126 Genghis Khan the Conqueror(最小生成树+树形DP)
- HDU 4126 Genghis Khan the Conqueror (树形DP+MST)
- HDU - 4126 Genghis Khan the Conqueror(树形DP + 最小生成树)
- 树形dp+MST-hdu-4126-Genghis Khan the Conqueror
- hdu-4126 Genghis Khan the Conqueror(最小生成树+树形dp)
- HDU 4126 Genghis Khan the Conqueror MST+树形dp
- 树形dp+MST-hdu-4126-Genghis Khan the Conqueror
- HDU 4126 Genghis Khan the Conqueror (树形DP + MST)
- Genghis Khan the Conqueror----HDU_4126----最小生成树_DFS_DP
- hdu4126 Genghis Khan the Conqueror Prim + 树形dp
- hdu4126 Genghis Khan the Conqueror--最小生成树 & 树形DP(待解决)
- 树形dp hdu 4126 Genghis Khan the Conqueror
- HDU 4126 Genghis Khan the Conqueror MST+最佳替代边
- hdu4126 Genghis Khan the Conqueror 树形dp+最小生成树
- HDU 4126 POJ 4006 Genghis Khan the Conqueror
- hdu 4126 Genghis Khan the Conqueror 最小生成树变形