hdu4126 Genghis Khan the Conqueror Prim + 树形dp

好题,学到了很多新姿势。

题意:在一棵mst上,修改一些边的值(此边有可能不在MST上),Q次操作(每次只是在原图上修改),求修改后的MST总和.

题解:首先Prim 求出MST    (n^2)

    对于每次修改,即相当于把MST上一条边截掉,原来的MST变成了两棵树,可以证明修改后的新MST一定包含这两棵树,也就是说只需要找到连接两棵树的最短边即可。

    好了,关键问题就是怎样找连接两棵树上的最短边。可以用(n^2)的dp进行预处理,然后每次o(1)查询即可。

   我们用dp[i][j] 表示 树A(包含i的树) 到 树B(包含j的树)的最短边长。类似floyd的更新处理。具体看代码。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int N = 3000 + 10;
const int inf = 1e8;
vector<int> ed[N];
vector<int> son[N];
typedef long long ll;
ll map[N][N],sum;
int fa[N],dis[N],pre[N],n,m,u,v,w,q;
int dp[N][N];
void prim()
{
    sum = 0;
    for(int i=1;i<=n;i++) ed[i].clear();
    int now=1,min_p,min_dis;
    for(int i=1;i<=n;i++) dis[i] = inf;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(now != j &&dis[j] > 0 && map[now][j] < dis[j]){
                dis[j] = map[now][j];
                pre[j] = now;
            }
        }
        min_p = -1; min_dis = inf;
        dis[now] = -1;
        for(int j=1;j<=n;j++) if(dis[j] > 0 && dis[j] < min_dis){
            min_p = j;
            min_dis = dis[j];
        }
        if(min_p==-1) break;
        ed[min_p].push_back(pre[min_p]);
        ed[pre[min_p]].push_back(min_p);
        now = min_p;
        sum += min_dis;
    }
}
void bbu(int x,int pa)
{
    for(int i=0;i<ed[x].size();i++) if(ed[x][i]!=pa){
        son[x].push_back(ed[x][i]);
        fa[ed[x][i]] = x;
        bbu(ed[x][i],x);
    }
}
void build()
{
    for(int i=1;i<=n;i++) fa[i] = i;
    for(int i=1;i<=n;i++) son[i].clear();
    bbu(1,0);
}
//注意下面的更新
ll dfs(int p,int u,int fa) //求p到 u及u的子树的最短边
{
    ll ans = inf;
    for(int i=0;i<ed[u].size();i++) if(ed[u][i]!=fa)
    {
        int v = ed[u][i];
        int tmp = dfs(p,v,u);
        ans = min(ans,(ll)tmp);
        dp[u][v] = dp[v][u] = min(dp[u][v],tmp);
    }
    if(p != fa)
        ans = min(ans,map[p][u]);
    return ans;
}
void init()
{
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=inf;
    for(int i=1;i<=n;i++)
        dfs(i,i,-1);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0 && m==0) break;
        double ans = 0.0;
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) map[i][j] = inf ;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&v,&w);
            u++; v++;
            map[u][v] = map[v][u] = w;
        }
        for(int i=1;i<=n;i++) map[i][i] = 0;
        prim();
        build();
        init();
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            u++;v++;
            int fl = 1;
            if(fa[u]!=v && fa[v]!=u) fl=0;
            if(fl==0)
                ans += sum;
            else
                ans += sum - map[u][v] + min(w , dp[u][v]);
        }
        printf("%.4f\n",ans/q);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值