Дерево!#12252

Дерево! (Розбір)

Автор проблеми:

Тимош

Підготовлено:

Хасан Салех (MathModel)

Редаговано:

Хасан Салех (MathModel)

Рішення

Назвемо вузол листком, якщо його ступінь . Нехай буде загальною кількістю листків у дереві.

Закріпіть дерево на . Для кожного вузла нехай буде кількістю листків у піддереві (відносно цього кореня). Ми можемо обчислити за один DFS:

s[v] \;=\; [\deg(v)=1] \;+\; \sum_{u\in \text{діти}(v)} s[u].

Розглянемо будь-яке ребро , де є батьком . Якщо ми розрізаємо це ребро, з'являються два компоненти:

- Сторона має листків, але ступінь зменшується на . Вузол стає листком якщо і тільки якщо його ступінь була , а листок перестає бути листком якщо і тільки якщо його ступінь була . Тому кількість листків на стороні після розрізання дорівнює

A \;=\; s[v] \;+\; [\deg(v)=2] \;-\; [\deg(v)=1].

- Сторона має L-s[v] листків, і втрачає одне інцидентне ребро, тому його ступінь також зменшується на . Таким чином, кількість листків на стороні після розрізання дорівнює

B \;=\; (L-s[v]) \;+\; [\deg(p)=2] \;-\; [\deg(p)=1].

Ребро є збалансованим тоді і тільки тоді, коли A=B. Ми можемо обчислити L=s[1] з першого DFS, а потім у другому DFS по ребрах батько-дитина перевірити рівність вище і підрахувати, скільки разів вона виконується.

Ескіз коректності

Розрізання змінює статус листка лише на кінцевих точках і (всі інші зберігають той же ступінь), тому коригування на +[\deg=2]-[\deg=1] є як необхідним, так і достатнім. Оскільки і L-s[v] рахують листки строго всередині кожної сторони перед врахуванням змін ступеня кінцевих точок, A=B точно характеризує збалансовані ребра.

Складність на тест (два обходи DFS).

#include <bits/stdc++.h>
using namespace std;

#define int int64_t
using vi=vector<int>;

void solve(){
    int n;cin>>n;
    vector<vi> g(n+1);
    for(int i=0,u,v;i<n-1;i++){
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    vector<int> s(n+1);
    vector<char> vis(n+1,0);

    auto tour= [&](auto self,int v,int p)->void{
        vis[v]=1;
        s[v]+= (int)g[v].size()==1;
        for(int u:g[v]) if(u!=p) self(self,u,v), s[v]+=s[u];
    };
    tour(tour,1,0);
    int L=s[1], ans=0;
    fill(vis.begin(),vis.end(),0);

    auto dfs= [&](auto self,int v,int p)->void{
        vis[v]=1;
        for(int u:g[v]) if(u!=p){
            int A = s[u] + ((int)g[u].size()==2) - ((int)g[u].size()==1);
            int B = L - s[u] + ((int)g[v].size()==2) - ((int)g[v].size()==1);
            ans += (A==B);
            self(self,u,v);
        }
    };
    dfs(dfs,1,0);
    cout<<ans<<"\n";
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t; if(!(cin>>t)) return 0;
    while(t--) solve();
    return 0;
}