题意: 给出一幅3000个点的图,有10000次操作: 求将某条边的权值变大后的最小生成树,最后输出10000次操作得到的最小生成树权值的平均值。
解法: 首先,对于一棵最小生成树,如果我们将它的某条边增大,那么存在一棵新的最小生成树与原生成树只有一条边是不同的,并且这条边与那条增大边在同一个环里,并且是与这条增大边同环的原最小非树边。
证明如下:
由最小生成树的定义,最小生成树是一棵具有最小权值的无向无环图T,那么可知,对于任意一条非T边x,若将x加入树中,必然会形成一个环,并且环中的任意一条树边的权值都不会比x大(若存在比x大的边,那么显然可以通过将其替换成x来得到一棵更小的生成树,显然与T是最小生成树不符)。那么不妨将最小生成树的定义修改为:原图的一个无环的边集合,并且将图中的任意一条集合外的边加入集合中,都将形成环,并且该边的权值不小于环中的最大边权。
那么当我们将一条边x的权值变大时,若它不是树边,则不影响最小生成树。若它是树边,则可以通过用与其成环的小权值非树边s来替换它,以得到更小的生成树T`。我们现在考虑这样替换形成的生成树T`是不是最小的:假设T`不是当前最小生成树,则存在一条非树边s`,能够替换T`中的某条与s`成环的边以得到更小的生成树。考虑这条非树边s`的来源:有两种可能,一种是原最小生成树T的非树边,若该边与T`中的s之外的任意边成环,则等价于s`在T中成环,而由T的定义,T中的边已经是不可被替换的了。另一种情况,若s`是x,而x是用s在T中替换出来的,再用x与T'中某条非s边:d进行替换,等价于s直接替换d,即s能够替换d得到更小的生成树,而s本是T非树边,d是T的树边,则说明T不是最小生成树,与假设矛盾。证毕。
因此对于这题,我们只需要找到与每条树边对应的成环边中最小的那条(称为替换边),与修改后的权值进行比较,即可O(1)得到新的最小生成树。而如何找替换边呢?我们按照Kruskal选边的顺序,当我们遇到一条非树边s时,树中与该边成环的未染色的边的最佳替换边显然就是s了。
存在另外一个问题,染色的过程中,每次都要求出lca,然后检查路径上的未染色边,每次检查可能需要O(n)的时间,总共n^2条边,即有n^2次检查,复杂度O(n^3)承受不起。
注意到,对已染色的边进行遍历是浪费时间,而树上的每个节点都只有一个父亲,那么可以用一个并查集,若该点到其父亲的边已经被染色,则将该点的fa直接指向其父亲,以避免对染色边的多次检查。这样,对每条非树边会扫描一次,对树边也只扫描一次,染色复杂度O(n^2)。
对于每次询问,原边权重x,修改边权重x`,替换边权重c,原最小生成树权和sum,
则新生成树权和
sum` = sum - x + min(x`,c) (该边为原树边)
sum (该边非树边)
/* Created Time: Saturday, November 09, 2013 AM11:37:29 CST */
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long lld;
const int INF = 0x3f3f3f3f;
const int N = 3333;
const int POW = 13;
int n,m;
struct E {
int u,v,w,next;
void read() {
scanf("%d%d%d",&u,&v,&w);
}
bool operator < (const E &tt) const {
return w<tt.w;
}
}edge[N*N],g[N<<1];
struct Node {
int fat,id;
}nod[N];
int etot,head[N],fa[N],p[N][13],dep[N],idx,tim;
int mat[N][N],org[N],tak[N];
lld sum;
void add_edge(int u,int v,int w) {
g[etot].v = v; g[etot].u = u;
g[etot].w = w; g[etot].next = head[u];
head[u] = etot ++;
}
int Find(int x) { return fa[x]==x ? x : fa[x] = Find(fa[x]); }
bool merge(int a,int b) {
a = Find(a),b = Find(b);
if (a==b) return false;
fa[a] = b;
return true;
}
void dfs(int u,int fid,int deep) {
if (fid!=-1) {
nod[u].id = idx;
nod[u].fat = g[fid].u;
mat[g[fid].u][g[fid].v] = idx;
mat[g[fid].v][g[fid].u] = idx;
org[idx] = g[fid].w;
tak[idx] = INF;
idx ++;
p[u][0] = g[fid].u;
} else {
nod[u].id = -1;
nod[u].fat = -1;
}
for (int i = 1; i < 13; i ++)
p[u][i] = p[p[u][i-1]][i-1];
dep[u] = deep;
for (int i = head[u]; i != -1; i = g[i].next) {
int v = g[i].v;
if (fid!=-1 && g[fid].u==v) continue;
dfs(v,i,deep+1);
}
}
int lca(int a,int b) {
if (dep[a]<dep[b]) swap(a,b);
int delta = dep[a]-dep[b];
for (int i = 0; i < POW; i ++)
if (delta>>i&1) a = p[a][i];
if (a!=b) {
for (int i = POW-1; i >= 0; i --)
if (p[a][i]!=p[b][i])
a = p[a][i],b = p[b][i];
a = p[a][0];
}
return a;
}
void jump(int a,int x,int w) {
x = Find(x);
a = Find(a);
while (a!=x) {
tak[nod[a].id] = w;
fa[a] = nod[a].fat;
a = Find(nod[a].fat);
}
}
void make_col(int a,int b,int w) {
int x = lca(a,b);
jump(a,x,w);
jump(b,x,w);
}
void work() {
sort(edge,edge+m);
for (int i = 0; i < n; i ++) head[i] = -1;
etot = 0;
sum = 0;
tim = 0;
idx = 0;
for (int i = 0; i < n; i ++) fa[i] = i;
memset(p,-1,sizeof(p));
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
mat[i][j] = -1;
for (int i = 0; i < m; i ++)
if (merge(edge[i].u,edge[i].v)) {
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
add_edge(u,v,w);
add_edge(v,u,w);
sum += w;
}
dfs(0,-1,0);
for (int i = 0; i < n; i ++) fa[i] = i;
for (int i = 0; i < m; i ++) {
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
if (mat[u][v]!=-1) continue;
make_col(u,v,w);
}
}
void sol() {
int nq;
scanf("%d",&nq);
lld fuc = 0;
for (int i = 0; i < nq; i ++) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int id = mat[a][b];
if (id==-1) {
fuc += sum;
} else {
lld tmp = sum-org[id];
tmp += min(c,tak[id]);
fuc += tmp;
}
}
printf("%.4f\n",fuc*1.0/nq);
}
int main() {
while (~scanf("%d%d",&n,&m),n||m) {
for (int i = 0; i < m; i ++)
edge[i].read();
work();
sol();
}
return 0;
}