题意:一个N个点的无向图,先生成一棵最小生成树,然后给你Q次询问,每次询问都是x,y,z的形式, 表示的意思是在原图中将x,y之间的边增大(一定是变大的)到z时,此时最小生成树的值是多少。最后求Q次询问最小生成树的平均值。 N<=3000 , Q<=10000。
思路:树形dp好题,想了一天加看题解才做出来。不难想出,如果更改边权的那条边不在最小生成树中,那么这次修改后的结果还是最小生成树的值。
问题是如果修改的这条边恰好在最小生成树中,那么情况就比较复杂,我们假设最小生成树上有两个相邻结点i,j,那么以i为根的子树到以j为根的子树的距离(连接两树的最小边权)为dp[i][j],
那么如果当前修改的是i到j之间的边,那么最后的答案就是最小生成树的值减去修改前的权值加上修改后的权值和dp[i][j]之间的较小值,这个可以用反证法来证,也很简单。
这道题的难点是求dp数组,如果纯暴力来做,时间上无法承受,这道题有一个很巧妙的做法,对于每一个结点u,以它为根进行一次dfs(dfs的是最小生成树上的边),这个dfs求的是u到某一颗子树的最小距离,这样我们就可以在本次dfs时更新所有dp值。
还是拿i和j距离,其实这一步的意思就是求出i中某个节点到j子树的最小距离,那么我们进行n次dfs也就把i,j子树中的所有节点都枚举了一次,也就成功求出了dp[i][j],这样每一次dfs的时间复杂度为O(n),共进行了n次dfs,所以时间复杂度为O(n^2)。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
const int MAXN = 3100;
const int INF = 0x3f3f3f3f;
bool vis[MAXN];
int lowc[MAXN], cost[MAXN][MAXN], pre[MAXN];
int n, m;
vector<int> G[MAXN];
int dp[MAXN][MAXN];
int Prim() {
int ans = 0;
memset(vis, 0, sizeof(vis));
vis[1] = true;
for(int i = 2; i <= n; i++) pre[i] = 1;
for(int i = 2; i <= n; i++) lowc[i] = cost[1][i];
for(int i = 2; i <= n; i++) {
int minc = INF;
int p = 0;
for(int j = 1; j <= n; j++)
if(!vis[j] && minc>lowc[j]) {
minc = lowc[j];
p = j;
}
if(minc == INF) return -1; //原图不连通
ans += minc;
vis[p] = true;
G[p].push_back(pre[p]);
G[pre[p]].push_back(p);
for(int j = 1; j <= n; j++)
if(!vis[j] && lowc[j]>cost[p][j])
lowc[j] = cost[p][j], pre[j] = p;
}
return ans;
}
int dfs(int pos, int cur, int fa) {
int ans = INF;
if(pos != fa) ans = min(ans, cost[pos][cur]);
for(int i = 0; i < G[cur].size(); i++) {
int u = G[cur][i];
if(u == fa) continue;
int tmp = dfs(pos, u, cur);
dp[cur][u] = dp[u][cur] = min(tmp, dp[cur][u]);
ans = min(ans, tmp);
}
return ans;
}
int main() {
//freopen("input.txt", "r", stdin);
while(scanf("%d%d", &n, &m)==2 && n) {
memset(cost, INF, sizeof(cost));
memset(dp, INF, sizeof(dp));
for(int i = 1; i <= n; i++) G[i].clear();
for(int i = 1, u, v, d; i <= m; i++) {
scanf("%d%d%d", &u, &v, &d);
u++; v++;
cost[u][v] = cost[v][u] = d;
}
int ans = Prim();
for(int i = 1; i <= n; i++) dfs(i, i, -1);
int q; cin >> q;
LL sum = 0;
for(int i = 1, u, v, d; i <= q; i++) {
scanf("%d%d%d", &u, &v, &d);
u++; v++;
if(pre[u]==v || pre[v]==u) sum += ans-cost[u][v]+min(d, dp[u][v]);
else sum += ans;
}
printf("%.4f\n", (double)sum / q);
}
return 0;
}