最裸的最小树形图(←现在才学的弱渣)。
显然只需要打一下一个堡垒,然后剩下的可以最后用最小的代价再打。
然后只要把图建出来跑一下朱-刘算法即可。
简单讲一下朱-刘算法吧(思想还是很简单的),下面只考虑图连通的情况:
首先为每一条边定一条边权最小的入边,并将所有这些入边的和累加入答案。那么如果没有环,显然现在所有的入边就构成了最小树形图;
否则,将构成的环缩成一个点,注意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