Genghis Khan the Conqueror 【HDU - 4126】【最优比例生成树】

题目链接


  看到这道题的时候,我第一反应就是次优比例生成树的变形,但是,思路是这样的没错,却又少许不同的地方,我们来讲一下这里的不同点,依旧是要用到pre[]前缀来记录每个节点的前缀,然后判断的是每个边:若删除这条边,用其他边进行补,会需要多少的最小花费边。

  于是问题化简为:我们将[L, R]这条边的价值变为x,需要判断的是:(一)若是这条边是最优比例生成树上的边,那么除去这条边之后的连接两个独立的树的最小代价,就是第一棵树去匹配第二棵树,找到最小解;(二)若是这条边不是最优比例生成树上的边,那么好办了,直接不管就是了,加上最优比例生成树上的权值就可以了。

  最优比例生成树好建,怎么判断除去某条边后的连接两棵树的最小权重?那么就有点类似于树形DP,不过只是思维的类似,方法上的处理会简单好多。我们先建立这颗最优比例生成树(这永远是先决条件),然后,我们把树上的节点放进链中,就是树上的一个点需要包含所有与它相连的子节点,这个的目的在于处理后面的除去某边的最短距,我这不用vector<>而是用了链式前向星来做一个时间上的优化,其实差不多。然后,最为重要的就是我们处理的方式,我们利用dfs(目前访问节点,目前访问节点的根),其中还要知道一个全局变量就是根节点(出发节点),这样的一个思路来想,若是从根节点出发,我们会走向两边,但是就是不能走到之前的根节点:光说没用,赋上代码讲:

int dfs(int st, int fa)
{
    int len = INF;
    for(int u=head[st]; u!=-1; u=rode[u].next)
    {
        int v=rode[u].to;
        if(v!=fa) len = min(len, dfs(v, st) );
    }
    if(st!=rt && !used[st][rt])
    {
        len = min(len, edge[st][rt]);
    }
    path[st][fa] = path[fa][st] = min(len, path[fa][st]);
    return len;
}

  其中我的len定义为旗下的访问边中最小的距离根节点rt的距离,之后,我们去看目前访问节点与根节点的关系,若不是根节点并且不与根节点构成生成树上的边,我们就去优化len的大小,然后最后更新下path[目前访问节点][其的根](path指的就是去这边以后其他边等效该边的最优解)的距离。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define INF 0x3f3f3f3f
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int maxN=3005;
int N, M, Q, edge[maxN][maxN], path[maxN][maxN], pre[maxN], head[maxN*maxN], cnt, rt;
bool used[maxN][maxN];
struct Eddge
{
    int next, to, val;
    Eddge(int a=-1, int b=0, int c=0):next(a), to(b), val(c) {}
}rode[maxN*maxN];
void addEddge(int u, int v, int val)
{
    rode[cnt] = Eddge(head[u], v, val);
    head[u] = cnt++;
}
void init()
{
    cnt=0;
    for(int i=0; i<N; i++)
    {
        for(int j=i+1; j<N; j++)
        {
            edge[i][j] = edge[j][i] = path[i][j] = path[j][i] = INF;
            used[i][j] = used[j][i] = false;
        }
    }
    memset(head, -1, sizeof(head));
}
ll prime(int pos)
{
    ll ans=0;
    bool vis[maxN]; memset(vis, false, sizeof(vis));
    int lowercost[maxN];
    for(int i=0; i<N; i++) if(i!=pos){ lowercost[i]=edge[pos][i]; pre[i]=pos; }
    vis[pos]=true;  pre[pos]=-1;
    for(int line=1; line<N; line++)
    {
        int minn=INF;
        for(int i=0; i<N; i++)
        {
            if(!vis[i] && minn>lowercost[i])
            {
                minn=lowercost[i];
                pos=i;
            }
        }
        vis[pos]=true;
        addEddge(pos, pre[pos], minn);
        addEddge(pre[pos], pos, minn);
        used[pos][pre[pos]] = used[pre[pos]][pos] = true;
        ans+=minn;
        for(int i=0; i<N; i++)
        {
            if(!vis[i] && lowercost[i]>edge[pos][i]) { lowercost[i]=edge[pos][i]; pre[i]=pos; }
        }
    }
    return ans;
}
int dfs(int st, int fa)
{
    int len = INF;
    for(int u=head[st]; u!=-1; u=rode[u].next)
    {
        int v=rode[u].to;
        if(v!=fa) len = min(len, dfs(v, st) );
    }
    if(st!=rt && !used[st][rt])
    {
        len = min(len, edge[st][rt]);
    }
    path[st][fa] = path[fa][st] = min(len, path[fa][st]);
    return len;
}
void solve()
{
    for(int i=0; i<N; i++)
    {
        rt = i;
        dfs(i, i);
    }
}
int main()
{
    while(scanf("%d%d", &N, &M) && (N | M))
    {
        init();
        for(int i=1; i<=M; i++)
        {
            int e1, e2, e3;
            scanf("%d%d%d", &e1, &e2, &e3);
            edge[e1][e2] = edge[e2][e1] = e3;
        }
        ll Minn_Tree=prime(0), ans=0;
        solve();
        scanf("%d", &Q);
        for(int i=1; i<=Q; i++)
        {
            int e1, e2, e3;
            scanf("%d%d%d", &e1, &e2, &e3);
            if(used[e1][e2]) ans += Minn_Tree - edge[e1][e2] + min(e3, path[e1][e2]);
            else ans += Minn_Tree;
        }
        printf("%.4lf\n", (double)ans/(double)Q );
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值