这题本质是求最小树形图。
在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。
首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环所称一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩的那个环的权和,就是原图中最小树形图的权。
上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。
code:
//
最小树形图,源点编号为1
#include
<
cstdio
>
#include
<
cstring
>
#include
<
cmath
>
using
namespace
std;
struct
Point {
double
_x,_y;
Point(
double
x,
double
y) :_x(x),_y(y) {}
Point() {}
};
const
unsigned
int
maxn
=
128
;
const
double
NOEDGE
=
99999999
;
double
G[maxn][maxn];
Point allp[maxn];
int
N,M;
double
res;
inline
double
dist(
const
Point
&
p1,
const
Point
&
p2) {
return
sqrt((p1._x
-
p2._x)
*
(p1._x
-
p2._x)
+
(p1._y
-
p2._y)
*
(p1._y
-
p2._y));
}
template
<
class
T
>
void
update(T
&
o,
const
T
&
x){
if
(o
>
x)
o
=
x;
}
bool
vis[maxn];
void
dfs(
int
v){
vis[v]
=
true
;
for
(
int
i
=
2
;i
<=
N;
++
i)
if
((
!
vis[i])
&&
G[v][i]
!=
NOEDGE)
dfs(i);
}
bool
possible(){
memset(vis,
0
,
sizeof
(vis));
dfs(
1
);
for
(
int
i
=
2
;i
<=
N;
++
i)
if
(
!
vis[i])
return
false
;
return
true
;
}
int
pre[maxn];
bool
del[maxn];
void
solve(){
int
num
=
N;
memset(del,
0
,
sizeof
(del));
for
(;;){
int
i;
for
(i
=
2
;i
<=
N;
++
i){
if
(del[i])
continue
;
pre[i]
=
i;
G[i][i]
=
NOEDGE;
for
(
int
j
=
1
;j
<=
N;
++
j){
if
(del[j])
continue
;
if
(G[j][i]
<
G[pre[i]][i])
pre[i]
=
j;
}
}
for
(i
=
2
;i
<=
N;
++
i){
if
(del[i])
continue
;
int
j
=
i;
memset(vis,
0
,
sizeof
(vis));
while
(
!
vis[j]
&&
j
!=
1
){
vis[j]
=
true
;
j
=
pre[j];
}
if
(j
==
1
)
continue
;
i
=
j;
res
+=
G[pre[i]][i];
for
(j
=
pre[i];j
!=
i;j
=
pre[j]){
res
+=
G[pre[j]][j];
del[j]
=
true
;
}
for
(j
=
1
;j
<=
N;
++
j){
if
(del[j])
continue
;
if
(G[j][i]
!=
NOEDGE)
G[j][i]
-=
G[pre[i]][i];
}
for
(j
=
pre[i];j
!=
i;j
=
pre[j]){
for
(
int
k
=
1
;k
<=
N;
++
k){
if
(del[k])
continue
;
update(G[i][k],G[j][k]);
if
(G[k][j]
!=
NOEDGE)
update(G[k][i],G[k][j]
-
G[pre[j]][j]);
}
}
for
(j
=
pre[i];j
!=
i;j
=
pre[j]){
del[j]
=
true
;
}
break
;
}
if
(i
>
N){
for
(
int
i
=
2
;i
<=
N;
++
i){
if
(del[i])
continue
;
res
+=
G[pre[i]][i];
}
break
;
}
}
}
int
main(){
double
x,y;
for
(;;){
if
(scanf(
"
%d%d
"
,
&
N,
&
M)
==
EOF)
return
0
;
if
(N
==
0
)
break
;
for
(
int
i
=
0
;i
<=
N;i
++
)
for
(
int
j
=
0
;j
<=
N;j
++
)
G[i][j]
=
NOEDGE;
for
(
int
i
=
1
;i
<=
N;i
++
) {
scanf(
"
%lf %lf
"
,
&
x,
&
y);
allp[i]._x
=
x;
allp[i]._y
=
y;
}
for
(
int
i
=
0
;i
<
M;
++
i){
unsigned
int
a,b;
scanf(
"
%u%u
"
,
&
a,
&
b);
update(G[a][b],dist(allp[a],allp[b]));
}
if
(
!
possible()){
puts(
"
poor snoopy
"
);
}
else
{
res
=
0
;
solve();
printf(
"
%.2f\n
"
,res);
}
}
}