题目传送门 P2081
一些定义:
定义 f a u f a u 表示 u u 的父亲,f a c n t u f a c n t u 表示 u u 的父节点个数(取值 1 1 或 2 2 ),s o n u s o n u 表示 u u 的儿子个数,c h u c h u 表示 u u 的子节点,d o w n u d o w n u 表示在以 1 1 为根的树中,从 u u 出发第一步向下走的期望路径长度。u p u u p u 表示从 u u 出发第一步向上走的期望路径长度,w i , j w i , j 表示 i i 到 j j 的边权。
那么对于每个点,其答案为 a n s u = 1 s o n u + f a c n t u × ( s o n u × d o w n u + u p u × f a c n t u ) a n s u = 1 s o n u + f a c n t u × ( s o n u × d o w n u + u p u × f a c n t u ) 。
最终答案即为 1 n n ∑ i = 1 a n s i 1 n ∑ i = 1 n a n s i 。
50pts 部分分(图为一棵树):
此时 f a c n t u = 1 f a c n t u = 1 。因此 d o w n d o w n 值不依赖 u p u p 值。其推导较为简单:
d o w n u = 1 s o n u ∑ v ∈ c h u ( d o w n v + w u , v ) d o w n u = 1 s o n u ∑ v ∈ c h u ( d o w n v + w u , v )
而对于 u p u p 值,需要依赖 d o w n f a u d o w n f a u 和 u p f a u u p f a u 。此时我们第一步必须走到 f a u f a u ,并且再下一步不能走回 u u 。那么式子就推出来了:
u p u = w u , f a u + f a c n t f a u × u p f a u + s o n f a u × d o w n f a u − d o w n u − w u , f a u f a c n t f a u + s o n f a u − 1 u p u = w u , f a u + f a c n t f a u × u p f a u + s o n f a u × d o w n f a u − d o w n u − w u , f a u f a c n t f a u + s o n f a u − 1
这样我们就能得到 50 p t s 50 p t s 的好成绩了。
另外 50pts(图为一棵基环树):
我们将基环树看成一些树将跟连成环的结果。以下简称环上节点为 “环点”,其余为 “树点”。如图所示是一棵基环树,其中每个节点连向其父节点。
所有环点的 f a c n t = 2 f a c n t = 2 ,树点的 f a c n t = 1 f a c n t = 1 。
首先,根据定义,每个点的 d o w n d o w n 值不变。而 u p u p 值的求得就较为麻烦。
其次,考虑推导过程,每个树点 的 u p u p 值仍可以通过上述公式求得。而对于环点就比较麻烦,因为每一步我们可以选择走到一个环点,或走进当前环点的子树。
我们先通过遍历求出一些值(因为要进行后面的计算,同时我们需要按保证搜到环点的顺序,将这些环点顺序相连,正好是原图中的环):
t t ,表示环点的数量。
d f n u d f n u ,表示这个点是第几个被搜到的环点 。
i d i i d i ,表示搜到的第 i i 个环点的标号。
d i s l i , d i s r i d i s l i , d i s r i ,表示在环上,第 i i 个被搜到的环点在环上的左右边权。
对于一个环点 u u ,我们强制它逆时针在环上走,那么其 u p u p 值公式为:
u p u = ∑ i , v = i d i p i × ( s o n v × d o w n v s o n v + 1 + w ) u p u = ∑ i , v = i d i p i × ( s o n v × d o w n v s o n v + 1 + w )
其中 i i 的取值分别为 d f n u + 1 , d f n u + 2 , … t , 1 , … d f n u − 1 d f n u + 1 , d f n u + 2 , … t , 1 , … d f n u − 1 。虽然它看起来很奇怪,但确实是这个式子。
其中 p i p i 表示走到这个环点的概率,由于我们限定第一步不进入子树,所以 p d f n u mod t + 1 = 1 p d f n u mod t + 1 = 1 。对于每个环点,我们有几率进入它的子树,或者继续在环上走,所以 p i mod t + 1 = p i × 1 s o n i d i + 1 p i mod t + 1 = p i × 1 s o n i d i + 1 。w w 表示从上个环点走过来的边权。需要注意,如果按逆时针下一个环点已经走过了(即走了一圈),我们只能进入该点的子树。
上面规定的是第一步逆时针走的情况,顺时针的情况同理,两者的 u p u p 值加起来 / 2 / 2 即为最终的 u p u p 值。或者直接将 p d f n u mod t + 1 p d f n u mod t + 1 设成 1 2 1 2 也可以。
最后将每个树点的 u p u p 值更新一遍即可。总复杂度为 O ( n + k 2 ) O ( n + k 2 ) ,k k 为环点个数。
#define pii pair<int, int>
#define Ld double
#define pb push_back
#define mp make_pair
int n, m;
vector<pii > e[N];
namespace Tree {
Ld f[N], g[N];
void dfs (int u, int Fa) {
int nw = 0 ;
for (pii x : e[u]) {
int v = x.fi, w = x.se;
if (v == Fa) continue ;
++nw;
dfs (v, u);
f[u] += f[v] + w;
}
if (nw) f[u] /= nw;
}
void dfs2 (int u, int Fa) {
for (pii x : e[u]) {
int v = x.fi, w = x.se;
if (v == Fa) continue ;
if (e[u].size () == 1 ) g[v] = w;
else g[v] = w + (g[u] + f[u] * (e[u].size () - (u != 1 )) - f[v] - w) / (e[u].size () - 1 );
dfs2 (v, u);
}
}
void solve () {
dfs (1 , 0 );
dfs2 (1 , 0 );
Ld ans = 0.0 ;
rep (i, 1 , n) {
ans += (f[i] * (e[i].size () - (i != 1 )) + g[i]) / e[i].size ();
}
printf ("%.5f\n" , ans / n);
}
}
int t, pos, fl;
int facnt[N], son[N];
int id[N], dfn[N];
int disl[N], disr[N];
bool vis[N];
Ld f[N], g[N];
void dfs (int u, int Fa) {
vis[u] = 1 ;
for (pii x : e[u]) {
int v = x.fi;
if (v == Fa) continue ;
if (vis[v]) {
pos = v;
return ;
}
dfs (v, u);
if (!fl && pos) {
if (pos == u) fl = 1 ;
return ;
}
if (fl) break ;
}
vis[u] = 0 ;
}
void dfs2 (int u, int Fa) {
id[++t] = u; dfn[u] = t;
for (pii x : e[u]) {
int v = x.fi, w = x.se;
if (dfn[v] || !vis[v] || v == Fa) continue ;
disr[t] = disl[t + 1 ] = w;
dfs2 (v, u);
}
}
void down (int u, int Fa) {
int cnt = 0 ;
for (pii x : e[u]) {
int v = x.fi, w = x.se;
if (vis[v] || v == Fa) continue ;
++cnt;
down (v, u);
f[u] += f[v] + w;
}
if (son[u] = cnt) f[u] /= cnt;
}
void up (int u, int Fa) {
for (pii x : e[u]) {
int v = x.fi, w = x.se;
if (vis[v] || v == Fa) continue ;
g[v] = w;
if (facnt[u] + son[u] - 1 ) g[v] += (g[u] * facnt[u] + f[u] * son[u] - f[v] - w) / (facnt[u] + son[u] - 1 );
up (v, u);
}
}
int pre (int x) { return x == 1 ? t : x - 1 ; }
int nxt (int x) { return x == t ? 1 : x + 1 ; }
void solve () {
dfs (1 , 0 );
rep (i, 1 , n) if (vis[i]) facnt[i] = 2 ; else facnt[i] = 1 ;
dfs2 (pos, 0 );
for (pii x : e[id[1 ]]) if (x.fi == id[t]) {
disl[1 ] = disr[t] = x.se;
break ;
}
rep (i, 1 , t) down (id[i], 0 );
rep (i, 1 , t) {
int u = id[i];
Ld p = 0.5 ;
int j = nxt (i); while (j != i) {
int v = id[j], w = disl[j];
if (nxt (j) == i) g[u] += p * (f[v] + w);
else g[u] += p * (f[v] * son[v] / (son[v] + 1 ) + w);
p /= son[v] + 1 ;
j = nxt (j);
}
p = 0.5 ;
j = pre (i); while (j != i) {
int v = id[j], w = disr[j];
if (pre (j) == i) g[u] += p * (f[v] + w);
else g[u] += p * (f[v] * son[v] / (son[v] + 1 ) + w);
p /= son[v] + 1 ;
j = pre (j);
}
}
rep (i, 1 , t) up (id[i], 0 );
Ld ans = 0.0 ;
rep (i, 1 , n) ans += (facnt[i] * g[i] + son[i] * f[i]) / (son[i] + facnt[i]);
printf ("%.5f\n" , ans / n);
}
int main () {
qread (n, m);
rep (i, 1 , m) {
int u, v, w;
qread (u, v, w);
e[u].pb (mp (v, w));
e[v].pb (mp (u, w));
}
if (m == n - 1 ) Tree::solve ();
else solve ();
return 0 ;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现