hdu 4126 prim+树形dp

链接

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

题意


一个N个点的无向图,先生成一棵最小生成树,然后给你Q次询问,每次询问都是x,y,z的形式, 表示的意思是在原图中将x,y之间的边增大(一定是变大的)到z时,此时最小生成数的值是多少。最后求Q次询问最小生成树的平均值。 N<=3000 , Q<=10000


解析


先用prim算法,求得最小生成树的权值,并且得到最小生成树的图(pre数组的记录他的前驱节点),若加的边在最小生成树外,不必考虑,结果就是最小生成树的权值;若在最小生成树上,就要把以前的边删除,形成两个子树,其子树之间的最小距离,就是最小结果。但是由于k的次数比较多,所以要先求出任意两个连通块的最小距离。其中只能用树形dp,来解决此问题。


代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn = 3000+100;
const int maxm = maxn*maxn;
int map[maxn][maxn];
int best[maxn][maxn];
int dp[maxn][maxn];
int vis[maxn];
vector<int>g[maxn];
int n, m;
int pre[maxn];
typedef long long LL;
void init() {
    memset(dp, inf, sizeof(dp));
    memset(best, 0, sizeof(best));
    memset(vis, 0, sizeof(vis));
    memset(map, inf, sizeof(map));
    for (int i=0; i<maxn; i++)
        g[i].clear(), pre[i] = -1; 
}

int prim() {
    int dis[maxn];
    int Dis=0;
    for (int i=0; i<n; i++) {
        dis[i] = map[0][i];
        pre[i] = 0;
    }
    pre[0] = -1;
    vis[0] = 1;
    dis[0] = inf;
    for (int i=0; i<n; i++) {
        int minn = inf, k = -1;
        for (int j=0; j<n; j++) {
            if (dis[j] < minn && !vis[j]) {
                minn = dis[j];
                k = j;
            }
        }
        if (k == -1)
            break;
        vis[k] = 1;
        Dis += dis[k];
        if (pre[k] != -1) 
            g[k].push_back(pre[k]), 
            g[pre[k]].push_back(k);
        for (int j=0; j<n; j++)
            if (!vis[j] && dis[j] > map[k][j])
                dis[j] = map[k][j], pre[j] = k;
    }
    return Dis;
}

int dfs(int u, int p, int rt) {    //树形dp
    int ans = inf;
    for (int i=0; i<g[u].size(); i++) {
        int v = g[u][i];
        if (v == p)
            continue;
        int t = dfs(v, u, rt);   //找到以v为根子树的连通块的最小距离
        ans = min(t, ans);
        dp[u][v] = dp[v][u] = min(dp[u][v], t);  //记录以u为根和以v为根连通块的最小距离
    }
    if (rt != p)    找到的边不能是最小生成树的边。
        ans = min(ans, map[rt][u]);
    return ans;
}


void solve() {
    for (int i=0; i<n; i++)
        dfs(i, -1, i);
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        init();
        if (n == 0 && m == 0)
            break;
        int Dis;
        for (int i=0; i<m; i++) {
            int u, v, c;
            scanf("%d%d%d", &u, &v, &c);
            map[u][v] = map[v][u] = c;
        }
        Dis = prim();
        for (int i=0; i<n; i++)
            printf("%d ", pre[i]);
        cout<<endl;
        solve();
        int K;
        scanf("%d", &K);
        double sum = 0;
        for (int i=0 ;i<K; i++) {
            int u, v, c;
            scanf("%d%d%d", &u, &v, &c);
            if (pre[u] != v && pre[v] != u)
                sum += Dis*1.0;
            else
                sum += (Dis-map[u][v]+min(dp[u][v], c))*1.0;
        }
        printf("%.4lf", sum/(K*1.0));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值