Hdu-4126 Genghis Khan the Conqueror //MST+树形dp

17 篇文章 1 订阅
3 篇文章 0 订阅

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=4126

题意

简而言之,就是给你一张图(稠密图),现在有 Q Q Q 次修改,每次依原图更改一条边的权值(只会比原图的大),让你求出每次更改后的最小生成树权值,取这 Q Q Q 次最小生成树权值的平均值

思路

我们先不考虑修改,就是求出原图的最小生成树,由于是稠密图,所以一般用 p r i m prim prim 算法,因为其和边没有关系,只在乎点的个数,复杂度 O ( n 2 ) O(n^2) O(n2)。在求解的过程中,我们需要保存最小生成树上的边,并建图(后面要用),设求得的最小生成树总权值为 s u m sum sum
现在考虑修改的影响,显然有两种情况:

  1. 要修改的边不在最小生成树上
  2. 要修改的边在最小生成树上

对于第一种情况,由于题目说了,修改的边只会比原来大,所以即使修改后最小生成树也还是没变,所以此组修改对总权值的贡献为 s u m sum sum
对于第二种情况,我们需要找这条边的最小替换边。不妨设要修改的边为 ( u , v ) (u,v) (u,v)、修改成的权值为 w 2 w_2 w2,最小生成树上这条边的权值为 w 1 w_1 w1,现在我们如果把最小生成树上的这条边拆掉,那么最小生成树就被拆成了两棵生成树,我们需要找的就是连接这两棵生成树的最小边(设其权值为 w w w)。则此组修改对总权值的贡献为 s u m − w 1 + m i n ( w , w 2 ) sum-w_1+min(w,w_2) sumw1+min(w,w2)。怎么做呢? ----树形dp
由于 n n n 不大,所以考虑 n 2 n^2 n2 做法,对于最小生成树上的每一条边,我们都尝试找出能替换它的最小边。
定义 d p [ u ] [ v ] dp[u][v] dp[u][v]:顶点 u u u v v v 分别在两棵生成树上时,将其联通的最小代价。
我们枚举根,将根和其它任意结点所在子树看成两棵生成树,我们一遍 d f s dfs dfs 回溯来更新当前根下,任意子树与根所在生成树联通的最小替换边,这样我们把每个点都作为根跑一次,就能将所有最小生成树的边的最小替换边找出来。具体转移看代码注释。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#define ft first
#define sd second
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=3e3+7;
const int inf=0x3f3f3f3f;
int n,m;
int ans;//最小生成树权值
int ma[maxn][maxn];//原图邻接矩阵
int pre[maxn];//pre[i]存i点的前驱
int dis[maxn];//dis[i]存点i和点pre[i]的距离
int dp[maxn][maxn];//dp[u][v]存边(u,v)的最小替换边权值
bool vis[maxn],is[maxn][maxn];
vector<int> g[maxn];//最小生成树邻接表

void prim(){//求原图最小生成树
    ans=0;
    for(int i=1;i<=n;i++)dis[i]=ma[1][i],pre[i]=1,vis[i]=0;
    vis[1]=1,pre[1]=-1;
    for(int i=1;i<n;i++){
        int mi=inf,p=0;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&dis[j]<mi){
                mi=dis[j];p=j;
            }
        }
        ans+=mi;
        if(pre[p]!=-1){
            g[pre[p]].pb(p);
            g[p].pb(pre[p]);
            is[pre[p]][p]=is[p][pre[p]]=1;
        }
        vis[p]=1;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&ma[p][j]<dis[j]){
                dis[j]=ma[p][j];
                pre[j]=p;
            }
        }
    }
}
int dfs(int u,int fa,int root){
    int mi=inf;
    for(int i=0;i<(int)g[u].size();i++){
        int v=g[u][i];
        if(v==fa)continue;
        //tmp为u和v点分属两个集合相连通的最小替换边的权值
        //这个权值是u为根子树中某个点到根的边权(不是树上的边)
        //只通过到根的距离来更新的话可能不是最优的
        //所以我们才要换根,来找到最优的那个距离
        int tmp=dfs(v,u,root);
        dp[u][v]=dp[v][u]=min(dp[u][v],tmp);
        mi=min(tmp,mi);
    }
    //如果fa是root,边(root,u)是最小生成树上的边
    //因为我们要找最小生成树外的边,所以不可选
    if(fa!=root)mi=min(mi,ma[root][u]);
    return mi;
}
int main() {
    while(~scanf("%d%d",&n,&m)){
        if(!n&&!m)break;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                ma[i][j]=dp[i][j]=inf;
                is[i][j]=0;
            }
            g[i].clear();
        }
        for(int i=0;i<m;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            ++u,++v;//范围调整为:[1,n]
            ma[u][v]=ma[v][u]=w;
        }
        prim();
        for(int i=1;i<=n;i++)dfs(i,0,i);
        int q;scanf("%d",&q);
        double sum=0;
        for(int i=0;i<q;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            ++u,++v;
            if(is[u][v]){//是最小生成树上的边
                sum+=ans-ma[u][v]+min(w,dp[u][v]);
            }
            else sum+=ans;
        }
        printf("%.4f\n",sum/q);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值