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;
}

正解总结

想到复杂的方法, 优化不了复杂度
考虑找规律找更简单的方法

posted @ 2024-10-14 15:06  Yorg  阅读(5)  评论(0编辑  收藏  举报