bzoj 4349: 最小樹形圖 朱-劉演算法
阿新 • • 發佈:2019-01-27
最裸的最小樹形圖(←現在才學的弱渣)。
顯然只需要打一下一個堡壘,然後剩下的可以最後用最小的代價再打。
然後只要把圖建出來跑一下朱-劉演算法即可。
簡單講一下朱-劉演算法吧(思想還是很簡單的),下面只考慮圖連通的情況:
首先為每一條邊定一條邊權最小的入邊,並將所有這些入邊的和累加入答案。那麼如果沒有環,顯然現在所有的入邊就構成了最小樹形圖;
否則,將構成的環縮成一個點,注意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