概念
树形图:相当于有向树,一种没有环且除根以外的点入度为
1
1
1的有向图。
最小树形图:和最小生成树相像。是在一个有向图中能遍历到所有点且边权和最小的树形图。
朱刘算法:一种求确定根的最小树形图的算法。
方法
- 给当前图 G G G中每一个点找一条最小的前驱边(除根外),将前驱边构成一个新图 G ′ G' G′。
- 如果 G ′ G' G′中没有环就加上所有边权并输出答案,否则就加上所有环内的边权并执行步骤 3 3 3。
- 将 G ′ G' G′中的环对应到 G G G上,所有指向 G ′ G' G′中的环的边 i , j i,j i,j的权值都减去 j j j的最小前驱边的权值。然后将 G ′ G' G′中的环对应到 G G G上进行缩点,将得到的新图代替 G G G,回到步骤 1 1 1。
正确性比较显然。如果 G ′ G' G′中没有环,此时除根外每个点都只找了一个前驱边,显然除根外每一个点度数为 1 1 1。而且我们每一个点都找的是最小的前驱边,所以一定是最小的。如果 G G G中有环,那就换掉环中一条边,那就找换掉后增加的边权最小的,此时 3 3 3步骤中更改边权的操作已经把边权换成了该成某条边后增加的权值,而步骤 1 1 1又刚好在找增加的最小的边。因为环缩成了一个点,所以执行下来只会替换掉一条边,显然是最优的策略。
例题
AcWing 2417
模板题。需要注意的是无解当且仅当有点无法从根结点到达。
#include<bits/stdc++.h>
using namespace std;
const int NN=104;
int low[NN],dfn[NN],scc[NN],cnt,scn,pre[NN],n,m;
double x[NN],y[NN],d[NN][NN],td[NN][NN],in[NN];
bool g[NN][NN],st[NN];
stack<int>stk;
double dist(int a,int b)
{
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void dfs(int u)
{
st[u]=true;
for(int i=1;i<=n;i++)
if(g[u][i]&&!st[i])
dfs(i);
}
bool check()
{
memset(st,false,sizeof(st));
dfs(1);
for(int i=1;i<=n;i++)
if(!st[i])
return false;
return true;
}
void tarjan(int u)
{
low[u]=dfn[u]=++cnt;
in[u]=true;
stk.push(u);
int v=pre[u];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
if(low[u]==dfn[u])
{
int sum;
scn++;
do
{
sum=stk.top();
stk.pop();
scc[sum]=scn;
in[sum]=false;
}while(sum!=u);
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%lf%lf",&x[i],&y[i]);
memset(g,false,sizeof(g));
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
if(a!=b&&b!=1)
g[a][b]=true;
}
if(!check())
{
puts("poor snoopy");
continue;
}
double res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(g[i][j])
d[i][j]=dist(i,j);
else
d[i][j]=1e9;
while(1)
{
for(int i=1;i<=n;i++)
{
pre[i]=i;
for(int j=1;j<=n;j++)
if(d[pre[i]][i]>d[j][i])
pre[i]=j;
}
memset(dfn,0,sizeof(dfn));
scn=cnt=0;
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
if(scn==n)
{
for(int i=2;i<=n;i++)
res+=d[pre[i]][i];
break;
}
for(int i=2;i<=n;i++)
if(scc[pre[i]]==scc[i])
res+=d[pre[i]][i];
for(int i=1;i<=scn;i++)
for(int j=1;j<=scn;j++)
td[i][j]=1e9;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(d[i][j]!=1e9&&scc[i]!=scc[j])
{
int a=scc[i],b=scc[j];
if(scc[pre[j]]==b)
td[a][b]=min(td[a][b],d[i][j]-d[pre[j]][j]);
else
td[a][b]=min(td[a][b],d[i][j]);
}
n=scn;
memcpy(d,td,sizeof(d));
}
printf("%.2lf\n",res);
}
return 0;
}