最小树形图 jsoi shopping

 

 

题目大意:最小树形图;

最小树形图:给你一个带权有向图,选出一些边构成一颗有根树,并使得这棵树的边权之和最小;

 

下面为基本流程.

最小树形图模型首先给定点集和一个根,若干条带权有向边,求从根出发的一个子图,边数为N-1,能从根到所有节点,并且边权和最小.

首先可以肯定的是,如果这个图从根走一遍后发现不连通,那么肯定无最小树形图.

否则每个仍在图中的点i记对于i点的最小权入边对应的入点为pre[i]

如果没有环,直接将所有cost[pre[i],i]相加记为ans

否则,缩环为点,记环为v,环上的任意点位vv,不在环上的任意点为u,将环上的边权和加入ans,改cost[v,u]=min{cost[vv,u]},cost[u,v]=min(cost[u,vv]-cost[pre[vv],vv])

再继续修改pre[i],并判断还有没有环.

这样改造后,对于任意一个环,最后加上某条入这条环的边,因为边权的改造,必将消去环上一条边,使得最小树形图中边位N-1且连通所有点.

by ld from ly

这个算法的复杂度最坏是O(NM)的。

ld发明了个带LAZY标号的可并堆。具体实现是用的斜堆,这样这道题的复杂度就可以通过数据结构优化,但我没看懂,大家可以去jasonzhu8的博客看。

我用的是递归,实现起来有点麻烦,有些操作是冗余的。。。

下面是代码。ans1是求最小树形图,ans2可以忽视。话说因为我没设cost1导致调了一上午。。。

 

c++版

#include <cstdio>
#include <cstdlib>
#include <cstring>
const int oo=1073741819;
double c[100][100],f[100][100],ans1,ans2,tmp[1000];
int pre[1000],b[1000],st[1000],ts[1000],v[1000],vis[1000],num[1000];
int l,r,n,m,time,flag;
int find(int x) {if (b[x]!=x) b[x]=find(b[x]);return b[x];}
double min(double x,double y) {return (x<y) ? x : y;}
void mysoul()
{
    int i,j;
    double cnt;
    l=0;
    for (;st[r]!=st[0];r--) ts[++l]=st[r];
    ts[++l]=st[r],r--;
    for (i=1;i<=l;i++) {
	for (j=0;j<=n;j++) f[ts[i]][j]=c[ts[i]][j],f[j][ts[i]]=c[j][ts[i]];
	b[ts[i]]=st[0];
	ans1+=c[pre[ts[i]]][ts[i]];
    }
    for (i=0;i<=n;i++) c[i][st[0]]=f[i][st[0]]-f[pre[st[0]]][st[0]];
    for (i=0;i<=n;i++)
	for (j=1;j<=l-1;j++) {
	    c[i][st[0]]=min(c[i][st[0]],f[i][ts[j]]-f[pre[ts[j]]][ts[j]]);
	    c[st[0]][i]=min(c[st[0]][i],f[ts[j]][i]);
	}
    cnt=oo;
    for (i=0;i<=n;i++) {
	if (b[i]!=i || i==st[0]) continue;
	if (pre[i]<oo) {
	    pre[i]=find(b[pre[i]]);
	    if (c[st[0]][i]<c[pre[i]][i]) pre[i]=st[0];
	}
	else if (c[st[0]][i]<oo) pre[i]=st[0];
	if (c[i][st[0]]<cnt) cnt=c[i][st[0]],pre[st[0]]=i;
    }
    if (cnt>=oo) pre[st[0]]=oo;
    st[0]=oo;
}
void dfs(int x)
{
    v[x]=time,vis[x]=1;
    st[++r]=x;
    if (pre[x]<oo) {
	if (v[pre[x]]!=time) dfs(pre[x]);
	if (vis[pre[x]]==1) {
	    if (st[0]==oo) st[0]=pre[x];
	    else if (st[0]==x) {
		mysoul();
		flag=time;
	    }
	    return ;
	}
    }
    r--,vis[x]=2;
}
void mydream()
{
    int i;
    for (i=1;i<=n;i++) b[i]=i;
    ans1=0;
    for (flag=0;flag==time;) {
	time++;
	for (i=1;i<=n;i++) {
	    if (v[i]==time || b[i]!=i) continue;
	    st[r=0]=oo,dfs(i);
	}
    }
    for (i=0;i<=n;i++) {
	if (pre[i]>=oo || b[i]!=i) continue;
	ans1+=c[pre[i]][i];
    }
}
void init()
{
    int i,x,y,j;
    double z,cnt;
    scanf("%d\n",&n);
    memset(c,127,sizeof(c));memset(pre,127,sizeof(pre));
    for (i=1;i<=n;i++) {
	scanf("%lf%d\n",&tmp[i],&num[i]);
	c[0][i]=tmp[i];
    }
    scanf("%d\n",&m);
    for (i=1;i<=m;i++) {
	scanf("%d%d%lf\n",&x,&y,&z);
	c[x][y]=z,tmp[y]=min(tmp[y],z);
    }
    for (i=0;i<=n;i++) {
	cnt=oo;
	for (j=0;j<=n;j++) {
	    if (c[j][i]>=oo) continue;
	    if (c[j][i]<cnt) cnt=c[j][i],pre[i]=j;
	}
    }
    mydream();
    for (i=1;i<=n;i++) if (num[i]) ans2+=(num[i]-1)*tmp[i];
    printf("%.2lf\n",ans1+ans2);
}
int main()
{
    freopen("sxt1988.in","r",stdin);
    freopen("sxt1988.out","w",stdout);
    init();
    return 0;
}


 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值