最裸的最小树形图(←现在才学的弱渣)。
显然只需要打一下一个堡垒,然后剩下的可以最后用最小的代价再打。
然后只要把图建出来跑一下朱-刘算法即可。
简单讲一下朱-刘算法吧(思想还是很简单的),下面只考虑图连通的情况:
首先为每一条边定一条边权最小的入边,并将所有这些入边的和累加入答案。那么如果没有环,显然现在所有的入边就构成了最小树形图;
否则,将构成的环缩成一个点,注意n个点n条边只能构成一个最简单的环(因此不需要跑Tarjan强联通分量),那么就找到上面的一个点,走一圈重标号即可。
重复上述过程,直到没有环(显然只剩下一个点的时候也是没有环的)。
但是这样得到的答案显然是不对的。注意一个环中的某一个点x,再一次找x的最小入边的时候,根据流程要把x的最小入边的边权累加入答案,但是x原来的最小入边也累加入答案,显然这样是会重复的;那么不妨在缩点的时候让所有x的入边都减去当前x的最小入边。
AC代码如下:
#include<iostream> #include<cstdio> #include<cstring> #define N 20005 using namespace std; int n,m,cnt,tot,pre[N],num[N],vis[N],id[N],p[N]; double rch[N],c[N]; struct edg{ int x,y; double z; }e[N]; double solve(int rt){ int i,j,k; double tmp=0; while (1){ for (i=1; i<=n; i++) rch[i]=1e100; for (i=1; i<=m; i++) if (e[i].x!=e[i].y && e[i].z<rch[e[i].y]){ pre[e[i].y]=e[i].x; rch[e[i].y]=e[i].z; } rch[rt]=0; for (i=1; i<=n; i++) if (rch[i]==1e100) return -1; for (i=1; i<=n; i++) vis[i]=id[i]=0; cnt=0; for (i=1; i<=n; i++){ tmp+=rch[i]; for (k=i; vis[k]!=i && !id[k] && k!=rt; k=pre[k]) vis[k]=i; if (!id[k] && k!=rt){ id[k]=++cnt; for (j=pre[k]; j!=k; j=pre[j]) id[j]=cnt; } } if (!cnt) return tmp; for (i=1; i<=n; i++) if (!id[i]) id[i]=++cnt; for (i=1; i<=m; i++){ double t=rch[e[i].y]; e[i].x=id[e[i].x]; e[i].y=id[e[i].y]; if (e[i].x!=e[i].y) e[i].z-=t; } n=cnt; rt=id[rt]; } } int main(){ scanf("%d",&n); int i,x,y; double t; for (i=1; i<=n; i++){ scanf("%lf%d",&t,&x); if (x){ p[i]=++m; e[m].y=m; num[m]=x; c[m]=e[m].z=t; } } n=m+1; tot=m; for (i=1; i<n; i++) e[i].x=n; scanf("%d",&cnt); while (cnt--){ scanf("%d%d%lf",&x,&y,&t); if (p[x] && p[y]){ e[++m].x=p[x]; y=e[m].y=p[y]; e[m].z=t; c[y]=min(c[y],t); } } double ans=solve(n); for (i=1; i<=tot; i++) if (num[i]>1) ans+=(num[i]-1)*c[i]; printf("%.2f\n",ans); return 0; }
by lych
2016.3.31