Codeforces Round 903 (Div. 3) F. Minimum Maximum Distance

昊天djh發表於2024-08-13

https://codeforces.com/contest/1881/problem/F

不難發現一件事情,我們這裡最後的答案所在的點是 1 和 3 號點。

我們有沒有發現一個性質:就是這兩個點都是紅點間的路徑上的,而且最後的答案就是最長的紅點間的距離的長度除以二上取整。

那麼,我們怎麼找到最長的紅點間的距離呢?

很顯然,O(n^2)列舉兩個點然後求距離是會超時的。

這裡有一個比較奇妙的演算法,就是先欽定一個紅點為根節點,然後不停地刪除每一條邊上的葉子節點,直到這個葉子節點為紅點位置就停止在這一條樹枝上的操作。
接著,我們刪完點後,求出整棵樹的直徑,也就是紅點間的最長距離。

具體為什麼呢?

因為很顯然,我們刪完點後,所有的葉子結點都是紅色節點,而直徑本身就是從一個葉子節點到另外一個葉子節點,所以這個時候我們求出來的答案救贖對的。

最後我們只需要把直徑的長度除以二後向上取整即可。

這裡刪除節點的複雜度是 O(n) 的,而求直徑的dfs 也是 O(n) 的,所以最後的複雜度顯然就是 O(n) 的。

#include<bits/stdc++.h>
//#define x first
//#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=4e5+10,M=2010;
int n,k,res;
int e[N],ne[N],h[N],idx;
bool mark[N],st[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs1(int u,int father)//刪點
{
    int cnt=0;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==father) continue;
        dfs1(j,u);
        if(!st[j]) cnt++;
    }
    if(!cnt&&!mark[u]) st[u]=true;
}
int dfs2(int u,int father)//求樹的直徑
{
    int d1=0,d2=0;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==father||st[j]) continue;
        int d=dfs2(j,u)+1;
        if(d>d1) d2=d1,d1=d;
        else if(d>d2) d2=d;
    }
    res=max(res,d1+d2);
    return d1;
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);

    int t;
    cin>>t;
    while(t--)
    {
        memset(h,-1,sizeof h);
        memset(mark,0,sizeof mark);
        memset(st,0,sizeof st);
        cin>>n>>k;
        int root;
        while(k--)
        {
            int x;
            cin>>x;
            mark[x]=true;
            root=x;
        }
        for(int i=0;i<n-1;i++)
        {
            int a,b;
            cin>>a>>b;
            add(a,b);
            add(b,a);
        }
        dfs1(root,-1);
        res=0;
        dfs2(root,-1);
        cout<<(res+1)/2<<endl;
    }

    return 0;
}

相關文章