最小树形图:有向图,求以某个确定的点为根的外向最小生成树。
tarjan优化:https://www.luogu.com.cn/blog/xiaojiji/solution-p4716
简单地阐述一下想法:
贪心,合法解每个点(除了根)肯定有一条入边,于是每个点选择一条最小的入边,形成一个子图,如果有点没有入边无解。
这个子图的权值肯定小于等于最优解,然而并不一定是棵树,我们找出其中的环。
将环缩为点,所有连向这个环的边的权值改为原值减去对应点在环中入边的权值,并将答案加上环上边的权值。
此时一条指向环的入边代表替换了环中的一条边。
新图的最小树形图加上已有答案就是原图的最小树形图,考虑从原图的最小树形图构造出新图的最小树形图来证明:(截图自链接中的第二篇博客)
每一轮至少会减少一个点,每轮复杂度O(m),总复杂度O(nm)。
#include<cstdio>
#include<cstring>
#define maxn 105
#define maxm 10005
int n,m,r,ans,INF=1e9;
int x[maxm],y[maxm],cst[maxm];
int in[maxn],vis[maxn],scc[maxn],pre[maxn];
int zhuliu()
{
int ans=0,v,cnt=0;
while(1)
{
for(int i=1;i<=n;i++) in[i]=INF,scc[i]=vis[i]=0;
for(int i=1;i<=m;i++)
if(x[i]!=y[i]&&cst[i]<in[y[i]])
pre[y[i]]=x[i],in[y[i]]=cst[i];
in[r]=0;
for(int i=1;i<=n;i++) if(in[i]==INF) return -1;
for(int i=1;i<=n;i++)
{
ans+=in[i];//这里没有成环的也把边加入答案中,方便下面的修改,显然先加入一条边然后把周围的边减去这个权值后继续选边是等价的
for(v=i;!vis[v]&&v^r;v=pre[v]) vis[v]=i;
if(v!=r&&vis[v]==i)
{
scc[v]=++cnt;
for(v=pre[v];!scc[v];v=pre[v]) scc[v]=cnt;
}
}
if(!cnt) break;
for(int i=1;i<=n;i++) if(!scc[i]) scc[i]=++cnt;
for(int i=1;i<=m;i++)
{
v=y[i];
x[i]=scc[x[i]],y[i]=scc[y[i]];
if(x[i]!=y[i]) cst[i]-=in[v];
}
n=cnt;cnt=0;
r=scc[r];
}
return ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&r);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&x[i],&y[i],&cst[i]);
printf("%d\n",zhuliu());
}
LOJ#6510. 「雅礼集训 2018 Day8」A:求无根最小树形图
加一个虚根0连向所有的点边权inf,如果有解最多选一条,如果无解则最终ans>=2*inf
#include<bits/stdc++.h>
#define maxn 505
#define maxm maxn*maxn
#define LL long long
using namespace std;
const LL inf = 1ll<<50;
int n,m,x[maxm],y[maxm],vis[maxn],pre[maxn],scc[maxn],N,M;
LL w[maxm],in[maxn],ans;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%lld",&x[i],&y[i],&w[i]);
for(int i=1;i<=n;i++) x[++m]=0,y[m]=i,w[m]=inf;
while(1){
for(int i=1;i<=n;i++) in[i]=inf+1,scc[i]=vis[i]=0;
for(int i=1;i<=m;i++)
if(x[i]^y[i]&&w[i]<in[y[i]]) in[y[i]]=w[i],pre[y[i]]=x[i];
for(int i=1;i<=n;i++){
ans+=in[i]; int x=i;
for(;!vis[x]&&x;x=pre[x]) vis[x]=i;
if(x&&vis[x]==i){
scc[x]=++N;
for(int y=pre[x];y^x;y=pre[y]) scc[y]=N;
}
}
if(!N) break;
for(int i=1;i<=n;i++) if(!scc[i]) scc[i]=++N;
for(int i=1;i<=m;i++){
int u=scc[x[i]],v=scc[y[i]];
if(u^v) w[++M]=w[i]-in[y[i]],x[M]=u,y[M]=v;
}
n=N,m=M,N=M=0;
}
printf("%lld\n",ans>=inf+inf?-1:ans-inf);
}