http://acm.hdu.edu.cn/showproblem.php?pid=4126
题目的大意是给你一张连通图,共有Q次更改,每次在原图的基础上,增加一条边的权值,求每次更改后最小生成树的权的总和sum,并输出sum/Q;思路:对原图做一次pim(),求出最小生树,如果修改的权值不是最小生成树的边,那边最小生成树不变。否则,假设修改的边是(a,b)并把(a,b)的权值修改为c,那个我们把 (a,b)边从最小生成树中去掉,就形成两棵树T1,T2,那么最小生成树就可以由T1,T2加上连接 T1 和T2的权值最小的边。
证明:如果修改的值不在最小生成树上,由于修改是增加权值,对原图求最小生成树,增加的值不会影响结果,所以最小生成树不变。如果,修改的值在最小生成树上,那么把修改的边去掉形成两棵树T1,T2,我们可以肯定有一棵最小生成树包含T1,T2的所有边。首先,我们证明一个最生成树的性质。我们假设(u,v)是最小生成树上的边,我们在树小生成树上任意加边,使得形成包含 (u,v)边的环,可以肯定环中必有一条边(u',v'),使 w(u',v') >=w(u,v) ,否则,我们可以去掉(u,v),么新的生成树的权值就比现在的最小生成树的权值小,与其是最小生成树的条件矛盾。我们回到这个题目,我们不妨设最后的最小生成树,不包含T1上的任意边(u,v),那么把(u,v)边加到最小生成树上,则可以形成一个环,因为(u,v)在原图的最小生成树上,我们可以肯定可以找到一条边(u',v')使得w(u,v) <= w(u',v'),把(u,v)代替(u',v')可以得到一个和原本最小生成树值相同的生成树,即可以得到一个包含边(u,v)的最小生成树。因此,必定有一棵最小生成树包含T1,T2上的全部边,那么我们只要把T1,T2用最小代价连接起来就可以得到最小生树了。
我们回到实际的算法,题目的图共有N(1<=N<=3000)个点, Q (1<=Q<=10000)次修改。进行一个prim()求最小生成树要O(n^2)在约是3000*3000 的复杂度,关键是怎么求去掉最小生成树上边,求连接两棵树的最小代价。这一步我原本是想要用堆写,可以我发现写完后各种bug,无奈看了网上的题解(看这篇题解)说用dp可以解决。我们用dp[i][j],表示从j及j的子树到i的最小代价,则dp[i][j] = min(dp[i][k],k是j的直接子结点),由于要满足后效性,我们可以直接在树上做dp,也可以先对树进行求前序遍历 ,根据遍历 的次序进行dp。后一种方法,听起来比较复杂,但实际操作中两种没差多少,且在很多题目中,用后一种进行dp,可以比较灵活地优化。
下面是源代码:
#include
#include
#include
const int maxn = 3300;
const int maxm = maxn*maxn;
const int INF = 0x7f7f7f7f;
int n, m, mst, mark;
int cost[maxn];
//node's father;
int father[maxn];
int *fa = father;
//The son of each node
int son[maxn];
//Every node's ID
int dfn[maxn];
//the order;
int ndfs[maxn];
int dp[maxn][maxn];
int edge_sum;
int head[maxn];
int tree_head[maxn];
typedef struct Edge{
int v;
int w;
int next;
}Edge;
Edge edge[maxm];
Edge tree[maxn];
void init(){
mark = 0;
edge_sum = 0;
memset(head,-1,sizeof(head));
memset(father,-1,sizeof(father));
memset(tree_head,-1,sizeof(tree_head));
}
void getData(){
int a, b, c;
for(int i = 0; i < m; i++){
scanf("%d%d%d",&a,&b,&c);
edge[edge_sum].v = b;
edge[edge_sum].w = c;
edge[edge_sum].next = head[a];
head[a] = edge_sum++;
edge[edge_sum].v = a;
edge[edge_sum].w = c;
edge[edge_sum].next = head[b];
head[b] = edge_sum++;
}
}
int dis[maxn];
bool vist[maxn];
int closed[maxn];
void prim(){
int i, j;
memset(dis,0x7f,sizeof(dis));
memset(vist,false,sizeof(vist));
for(i = head[0]; i != -1; i = edge[i].next){
closed[edge[i].v] = 0;
dis[edge[i].v] = edge[i].w;
}
mst = 0;
dis[0] = INF;
vist[0] = true;
for(i = 0; i < n-1; i++){
int k = 0;
for(j = 1; j < n; j++)
if(!vist[j] && dis[j] < dis[k])
k = j;
mst += dis[k];
cost[k] = dis[k];
vist[k] = true;
father[k] = closed[k];
tree[i].v = k;
tree[i].next = tree_head[closed[k]];
tree_head[closed[k]] = i;
for(j = head[k]; j != -1; j = edge[j].next){
if(vist[edge[j].v]) continue;
if(edge[j].w > dis[edge[j].v]) continue;
closed[edge[j].v] = k;
dis[edge[j].v] = edge[j].w;
}
}
}
void dfs(int root){
son[root] = 1;
dfn[root] = mark;
ndfs[mark++] = root;
for(int i = tree_head[root];i != -1; i = tree[i].next){
dfs(tree[i].v);
son[root] += son[tree[i].v];
}
}
int min(int a, int b){
if(a < b) return a;
else return b;
}
inline void swap(int &a, int &b){
int c;
c = a;
a = b;
b = c;
}
void work(){
int i, j;
memset(dp,0x7f,sizeof(dp));
for(i = 0; i < n; i++)
for(j = head[i]; j != -1; j = edge[j].next)
if(fa[edge[j].v] != i && fa[i] != edge[j].v){
dp[i][edge[j].v] = edge[j].w;
}
for(i = 0; i < n; i++)
for(j = n-1; j > 0; j--)
dp[i][fa[ndfs[j]]] = min(dp[i][fa[ndfs[j]]],dp[i][ndfs[j]]);
}
int ans[maxn];
void qurry(){
memset(ans,-1,sizeof(ans));
int i, j, Q, x, y, c;
double sum = 0;
scanf("%d",&Q);
for(int ii = 0; ii < Q; ii++){
scanf("%d%d%d",&x,&y,&c);
if(father[x] == y) swap(x,y);
if(fa[x] != y && fa[y] != x){
sum += mst;
}else if(father[y] == x && ans[y] != -1){
sum += mst - cost[y] + min(ans[y],c);
}else{
ans[y] = INF;
for(i = 0; i < n; i++){
if(dfn[i] < dfn[y] || dfn[i] > dfn[y] + son[y] -1)
ans[y] = min(ans[y],dp[i][y]);
else
ans[y] = min(ans[y],dp[x][i]);
}
sum += mst - cost[y] + min(ans[y],c);
}
}
printf("%0.4lf\n",sum/Q);
}
int main(){
while(~scanf("%d%d",&n,&m)){
if(n == 0 && m == 0) break;
init();
getData();
prim();
dfs(0);
work();
qurry();
}
return 0;
}
Grastyele