1014 CW 模擬賽 B.旅行

Yorg發表於2024-10-14

題面

現在的題似乎都找不到原題了
掛個 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;
}

正解總結

想到複雜的方法, 最佳化不了複雜度
考慮找規律找更簡單的方法