最小树形图
来源:互联网 发布:合金装备 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啦
阅读全文
0 0
- 【最小树形图】最小树形图复习
- 最小树形图
- 最小树形图
- 最小树形图
- 最小树形图
- 最小树形图
- POJ_3164 最小树形图
- 最小树形图poj3164
- 最小树形图
- 最小树形图
- 最小树形图
- 最小树形图
- POJ3164-最小树形图
- 最小树形图
- 最小树形图
- 【最小树形图】
- 最小树形图
- 最小树形图
- 1052. 卖个萌 (20) PAT乙级真题
- ANR 分析解决
- Spring MVC的自动转换功能 HttpMessageConverter
- JAVA调用ImageCapOnWeb控件实现拍照功能
- angular API
- 最小树形图
- 如何一次终止同一名字的多个进程
- 分布式缓存Redis之配置文件redis.conf详解
- 设计模式(十四)——备忘录模式
- A07_文本文件的读写
- android Logcat打印格式说明
- vmware workstation 14 黑屏 解决方法
- unity创建小地图
- mysql日常 bug发现