最小树形图

6 篇文章 0 订阅
1 篇文章 0 订阅

简介

最小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。
摘自 百度百科·最小树形图

典例

洛谷P2792 小店购物https://www.luogu.org/problemnew/show/P2792
题意简介:给你n个物品,每个物品都有其单价和你要购买的数量(不能多买),现在有优惠条件(A,B,f)表示若你已经买过A(不论数量),那么你就能以优惠价f买B,你可以多次买东西,每次买一种,每一种东西可以买多次,求最少的花费
这道题中如果你某个物品要买x个,若x>1,那么显然另外的x-1个可以在每种物品都买过一个之后再买,问题就转化成每个物品都买一个,求最优顺序下的费用

做法

构图,对于这一题相当于一个root点向每个物品连一个原价的边,对于优惠条件可以由A向B连一条f的边,为了方便起见我把所有的有向边反向
一开始对于每一个物品都向它最小的一个父亲连边,这样是不是很像环套树呢?然后呢,简单的求一下每个联通块有没有环,有就把环缩起来(我是缩到一个新的节点里),用并查集维护,对于新的节点如何加边呢,如图
这里写图片描述
比如1,2,3点要缩成一个点4,怎么做呢,把1到2,2到3,3到1三条边的值加到ans里面,若1到2的边权为V1,1到root的边权为V2,那么4加到1的边权值为V2-V1意思是如果这个环由1连到root,那么V1就不用加就解决啦
附上代码,有注释:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf;
    char *lastin=ibuf+IN_LEN;
    const char *lastout=ibuf+OUT_LEN-1;
    inline char getchar_()
    {
        if(ih==lastin)lastin=ibuf+fread(ibuf,1,IN_LEN,stdin),ih=ibuf;
        return (*ih++);
    }
    inline void putchar_(const char x)
    {
        if(ih==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;
        *oh++=x;
    }
    inline void flush(){fwrite(obuf, 1, oh - obuf, stdout);}
}
using namespace fast_IO;
//#define getchar() getchar_()
//#define putchar(x) putchar_((x))
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){const T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(b==0)return a;return gcd(b,a%b);}
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    if(cu=='.')
    {
        while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    }
    if(fla)x=-x;
}
template <typename T> void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int maxn=102,INF=0x7f7f7f7f;
int n,k,needsum[maxn];
double price[maxn],miniprice[maxn],ans=0;
int head[maxn],tow[10002],nxt[10002],tmp;double vau[10002];//邻接表 
inline void addb(const int u,const int v,const double w)
{
    tmp++;
    nxt[tmp]=head[u];
    head[u]=tmp;
    tow[tmp]=v;
    vau[tmp]=w;
}
int fa[maxn];double va[maxn];//最小出边指向的点,最小出边的长度 
int gone[maxn];//是否被dfs过(0:没有,1:当前这轮被dfs,2:曾经被dfs) 
int belo[maxn];//朴素并查集
int search(const int u)
{
    if(belo[u]==u)return u;
    belo[u]=search(belo[u]);
    return belo[u];
}
int node;
bool sign=1;//判断是否已经是最小树形图 
inline void update(const int u)//更新u节点的最小出边 
{
    int best=0;
    for(register int i=head[u];~i;i=nxt[i])
        if(search(tow[i])!=search(u)&&vau[best]>vau[i])//出边不能是到自己的 
            best=i;
    fa[u]=tow[best];
    va[u]=vau[best];
}
int sta[maxn],top;//用栈存储dfs到的元素
void dfs(const int u)
{
    sta[++top]=u;
    gone[u]++;
    if(u==n*2+1)return;
    if(!gone[search(fa[u])])dfs(search(fa[u]));
    else if(gone[search(fa[u])]==1)
    {
        sign=0;
        node++;
        belo[node]=node;
        while(sta[top]!=search(fa[u]))
        {
            ans+=va[sta[top]];
            belo[sta[top]]=node;
            for(register int i=head[sta[top]];~i;i=nxt[i])
                addb(node,tow[i],vau[i]-va[sta[top]]);
            top--;
        }
        ans+=va[sta[top]];
        belo[sta[top]]=node;
        for(register int i=head[sta[top]];~i;i=nxt[i])
            addb(node,tow[i],vau[i]-va[sta[top]]);
        update(node);
    }
    gone[u]++;
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
    {
        scanf("%lf%d",&price[i],&needsum[i]),miniprice[i]=price[i];
        if(needsum[i])addb(i,n*2+1,price[i]);
    }
    scanf("%d",&k);
    for(register int i=1;i<=k;i++)
    {
        int u,v;double w;
        scanf("%d%d%lf",&u,&v,&w);
        if(needsum[u])miniprice[v]=min(miniprice[v],w);
        if(needsum[u]&&needsum[v])addb(v,u,w);
    }
    for(register int i=1;i<=n;i++)
        if(needsum[i]>1)
        {
            ans+=(double)(needsum[i]-1)*miniprice[i];
            needsum[i]=1;
        }
    vau[0]=INF;
    for(register int i=1;i<=n;i++)
        if(needsum[i])
            belo[i]=i;
    for(register int i=1;i<=n;i++)
        if(needsum[i])
            update(i);
    node=n;
    belo[n*2+1]=n*2+1;
    while(1)
    {
        sign=1;
        for(register int i=1;i<=n*2+1;i++)
            if(needsum[i]&&!gone[i])
            {
                dfs(i);
                top=0;
            }
        for(register int j=1;j<=n*2+1;j++)
            if(belo[j]!=j)gone[j]=2;
            else gone[j]=0;
        if(sign)break;
    }
    for(register int i=1;i<=n*2+1;i++)
        if(belo[i]==i)//未被缩起来的点 
            ans+=va[i];
    printf("%.2lf",ans);
    return flush(),0;
}

最小树形图其实也不是很难啊,写过环套树的人就觉得so easy啦

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值