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