第一问很好求,相当于是所有点到 $s$ 或 $t$ 中较近的点的最短路径之和。换句话说,把 $s$ 与 $t$ 分别作为起点,跑两遍最短路后得到 $diss,dist$ 分别表示最短路,则答案即为 $\sum\limits_{i = 1}^{n} \min (diss_i,dist_i)$。又由于题目给的是一棵树,所以其实距离就相当于普通的子树向父亲节点的转移。第一问的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void dfs (int u,int fa,ll *dis)
{
for (int i = head[u];i;i = nxt[i])
{
int v = to[i];
if (v == fa) continue;
dis[v] = dis[u] + val[i];
dfs (v,u,dis);
}
}
dfs (s,0,dis1);dfs (t,0,dis2);
for (int i = 1;i <= n;++i) ansdis[i] = min (dis1[i],dis2[i]),ans += ansdis[i];
printf ("%lld\n",ans);

第二问相当于是还原路径,再次跑两遍最短路,当跑到一个节点 $u$ 时,若发现 $ansdis_u$ 加上边权后恰好等于 $ansdis_v$,也就是说这是一个可能的还原方式,把这个点所在的边标记一下。

当所有点标记以后,尝试进行判断出入边。考虑第 $i$ 条双向边满足边的下标 $x,y$ 为 $2i,2i+1$ 时存在性质 $x \oplus 1 = y,y \oplus 1 = x$,所以当下标为奇数时相当于从起点 $s,t$ 的出边,在构造答案时需要注意出入恰好反向。于是我们便得到了第二问的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void solve (int u,int fa)
{
for (int i = head[u];i;i = nxt[i])
{
int v = to[i];
if (v == fa) continue;
if (min (dis1[v],dis2[v]) == ansdis[u] + val[i]) vis[v] = i;//能够还原时记录该点所在边
solve (v,u);
}
}
solve (s,0);solve (t,0);
for (int i = 1;i <= n;++i)//利用双向边的性质进行构造
{
if (!vis[i]) continue;
ty[vis[i] >> 1] = (vis[i] & 1) ? 1 : 2;//注意恰好相反
}
for (int i = 1;i < n;++i) printf ("%d",ty[i]);