有向图的类Prim算法,找有向图的最小生成树
树形图:
以某个点为根的一棵有向树,其边权之和为图中所有树形图中是最小的称为最小树形图。
(1) 除了根节点外对每个点选取一条边权最小的入边
(2)判断当前(选出的边)组成的图中有无环
1.若无环:则说明当前图已经为构造好的最小生成树,算法结束
2.若有环:进行第(3)步
(3)将构造的图进行强连通分量缩点,得到新图 G ′ G' G′,对于 G ′ G' G′中的所有边
1.如果是环中的边:直接删去
2.如果终点在环内(即新缩的点):更新此边权权值为 W − W 环 内 W-W_{环内} W−W环内
3.其他边:不变
然后继续从(1)开始迭代
当迭代完成后,所有选择的边的边权之和就是最终的答案。
由于复杂度是
O(nm)
,因此在存储图的时候不需要背邻接表的板子,直接背邻接矩阵的即可。
板子:
const int N = 110,M=2e4+10,INF=1e8;
int n,m,r;
int d[N][N],bd[N][N],g[N][N];
int pre[N],bpre[N];
int dfn[N],low[N],timestamp,stk[N],top;
int id[N],scc_cnt;
bool st[N],ins[N];
void dfs(int u){
st[u]=true;
for(int i=1;i<=n;++i)
if(d[u][i]<INF&&!st[i])
dfs(i);
}
bool check_con(){
memset(st,0,sizeof st);
dfs(r);
rep(i,1,n)
if(!st[i])
return false;
return true;
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u;ins[u]=true;
int j=pre[u];
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(ins[j]) low[u]=min(low[u],dfn[j]);
if(low[u]==dfn[u]){
int y;
scc_cnt ++;
do{
y=stk[top --];
ins[y]=false;
id[y]=scc_cnt;
}while(y!=u);
}
}
int zhuliu(){
int ans = 0;
while(true){
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);
timestamp=scc_cnt=0;
for(int i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
if(scc_cnt == n){
for(int i=1;i<=n;++i) {
if(i==r) continue;
ans += d[pre[i]][i];
}
break ;
}
for(int i=1;i<=n;++i){
if(i==r) continue;
if(id[pre[i]] == id[i]){
ans += d[pre[i]][i];
}
}
for(int i=1;i<=scc_cnt;++i)
for(int j=1;j<=scc_cnt;++j)
bd[i][j]=INF;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(d[i][j] < INF && id[i]!=id[j]){
int a=id[i],b=id[j];
if(id[pre[j]]==id[j])
bd[a][b]=min(bd[a][b],d[i][j]-d[pre[j]][j]);
else bd[a][b]=min(bd[a][b],d[i][j]);
}
}
}
r=id[r];
n = scc_cnt;
memcpy(d,bd,sizeof d);
}
return ans;
}
void solve(){
n=read(),m=read(),r=read();
rep(i,1,m) {
int u=read(),v=read(),w=read();
d[u][v]=min(d[u][v],w);
}
// rep(i,1,n) rep(j,1,n) debug(d[i][j]);
if(!check_con()) puts("-1");
else print(zhuliu());
}
模板题
在一个二维平面中求 一个最小树形图
const int N = 110, M = 10010, INF=1e8;
int n,m,k;
pdd q[N];
bool g[N][N];
double d[N][N],bd[N][N]; //d数组用来存距离,bd数组用来存储备份
int pre[N],bkppre[N]; //pre数组用来存储备份,bkppre数组用来存储前去的备份
int dfn[N],low[N],timestamp,stk[N],top;
int id[N],scc_cnt=0;
bool st[N],ins[N];
void dfs(int u){
st[u]=true;
rep(i,1,n) if(g[u][i]&&!st[i]) dfs(i);
}
bool check_con(){
memset(st,0,sizeof st);
dfs(1);
rep(i,1,n) if(!st[i]) return false;
return true;
}
double get_dist(int a,int b){
double dx = q[a].x-q[b].x;
double dy = q[a].y-q[b].y;
return sqrt(dx*dx+dy*dy);
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u,ins[u]=true;
int j=pre[u];
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(ins[j]) low[u]=min(low[u],dfn[j]);
if(low[u]==dfn[u]){
int y;
scc_cnt ++;
do{
y=stk[top --];
ins[y]=false;
id[y]=scc_cnt;
}while(y!=u);
}
}
double zhuliu(){
double ans= 0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(g[i][j]) d[i][j] = get_dist(i,j);
else d[i][j] = INF;
while(true) {
//找所有点的入点的最短边
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;
}
}
//tarjan找环
memset(dfn,0,sizeof dfn);
timestamp=scc_cnt=0;
for(int i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
//缩点后无环,累加答案后算法结束
if(scc_cnt == n) {
for(int i=2;i<=n;++i) ans += d[pre[i]][i];
break;
}
//累加所有环内的边
for(int i=2;i<=n;++i)
if(id[pre[i]]==id[i])
ans += d[pre[i]][i];
//清空bd数组,准备存储缩点完更新的边权
for(int i=1;i<=scc_cnt;++i)
for(int j=1;j<=scc_cnt;j++)
bd[i][j] = INF;
//遍历每一个点,然后根据缩点后的结果对边进行操作
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(d[i][j] < INF && id[i] != id[j]){
int a=id[i],b=id[j];
if(id[pre[j]] == id[j])
bd[a][b]=min(bd[a][b],d[i][j]-d[pre[j]][j]);
else bd[a][b] = min(bd[a][b], d[i][j]);
}
n = scc_cnt;
memcpy(d,bd,sizeof d);
}
return ans;
}
void solve(){
rep(i,1,n) scanf("%lf%lf",&q[i].x,&q[i].y);
memset(g,0,sizeof g);
while(m--){
int a=read(),b=read();
if(a!=b&&b!=1) g[a][b]=true;
}
if(!check_con()) puts("poor snoopy");
else printf("%.2lf\n",zhuliu());
}
//=================================
int main(){
while(~scanf("%d%d",&n,&m)){
solve();
}
return 0;
}