1014 CW 模拟赛 B.旅行
题面
现在的题似乎都找不到原题了
挂个 pdf
题面下载
算法
容易想到链和菊花图的做法, 需要注意的是计算深度只能用 \(\rm{dfs}\) 来跑, 不能保证链的顺序与输入顺序相同
对于 \(n, m \leq 10^3\), 观察暴力做法
暴力
容易发现对于每一个点, 都要由起点 \(1\) 开始, 先到达一条链上最深的点, 再折返跑过来
对于每一个 \(a_i, i \in [1, m]\) , 将 \(a_1 \sim a_i\) 打上标记, \(\rm{dfs}\) 搜索每一个点, 判断这个点下方是否还有有标记的点, 若没有统计这个点的深度, 减去起点深度之后乘以二, 最后减去终点到起点即可
暴力代码
int dfs2(int u=1,int fa=0){
int ret=0;
for(int v:e[u]){
if(v==fa)continue;
ret+=dfs2(v,u);
}
if(ret==0)return 2*usd[u];
else return ret+2;
}
暴力总结
\(\rm{dfs}\) 的递归特性使其能判断自己下方的情况
若从起点开始找终点困难, 考虑从每个终点拆分的去计算路径长度
树上的距离问题, 通常可以拆分成树上深度问题
正解
观察到暴力方式每一次加入新点都需要重新跑一边 \(\rm{dfs}\), 这显然是无必要的
如果能每次加入一个点, 计算这个点带来的路径变化即可
想到 LCA, 然而需要和前面每一个点求 LCA , 对时间复杂度优化不大 ( \(n, m\) 为同一数级 )
观察图的性质, 发现在 LCA 之后的路径都已经被前面的 \(\rm{dfs}\) 计算过了, 于是在每次 \(\rm{dfs}\) 计算时, 把经过的每一条边都标记起来, 如果遇到已经标记的边, 则无需计算
正解代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 100010;
int n, m, a[maxn], fa[maxn], dep[maxn], ans, vis[maxn];
vector<int> vec[maxn];
void dfs(int u, int f)
{
fa[u] = f;
dep[u] = dep[f] + 1;
for (auto v : vec[u])
if (v != f)
{
dfs(v, u);
}
}
void add(int u)
{
if (vis[u])
return;
int res = 1;
vis[u] = 1;
while (!vis[fa[u]])
u = fa[u], vis[u] = 1, res++;
ans += 2 * res;
}
signed main()
{
// freopen ("B.in", "r", stdin);
// freopen ("B.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for (int i = 1, u, v; i < n; i++)
{
scanf("%lld%lld", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
for (int i = 1; i <= m; i++)
{
scanf("%lld", &a[i]);
}
dep[0] = -1;
dfs(1, 0);
vis[1] = 1;
for (int i = 1; i <= m; i++)
{
add(a[i]);
printf("%lld\n", ans - dep[a[i]]);
}
return 0;
}
正解总结
想到复杂的方法, 优化不了复杂度
考虑找规律找更简单的方法