最小树形图

来源:互联网 发布:合金装备 mac 汉化 编辑:程序博客网 时间:2024/06/05 19:41

简介

最小树形图,就是给有向带权图中指定一个特殊的点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啦

原创粉丝点击