Підвісимо дерево за першу (будь-яку) вершину.
Позначимо G[v] - найменшу кількість вершин якими можемо покрити піддерево з коренем v, якщо вершину v берем NG[v] - найменшу кількість вершин якими можемо покрити піддерево з коренем v, якщо вершину v не берем.
Якщо ми не беремо вершину v, то всіх її дітей маємо взяти обов'язково (бо ребра між v і дітьми лишаться непокриті) отже NG[v] = sum (G[to]). to - всі сини вершини v.
Якщо ми беремо вершину V, то дітей можемо як взяти так і не взяти. Дивимось для кожного сина, чи його оптимальніше взяти чи ні. NG[v] = sum (min(G[to], NG[to])). to - всі сини вершини v.
Отже просто запустимо пошук в глибину (dfs) і під час обходу будемо рахувати значення G[v], NG[v].
відповідь буде min(G[1], NG[1]).
#include <bits/stdc++.h>
using namespace std;
#define DIM 100007
long long G[DIM],NG[DIM];
vector<int> A[DIM];
long long n;
void dfs(int v, int parent) {
G[v] = 1; //якщо вершину берем - то рахуємо її
NG[v] = 0; //якщо не берем, то не рахуємо
for(auto to : A[v]) //перевіряємо всіх дітей
if(to != parent) {
dfs(to, v); //запускаємо пошук в глибину для сина
G[v] += min( G[to], NG[to] ); //якщо вершина V в покритті, то дітей можна як брати так і не брати
NG[v] += G[to]; //ящо вершина V не в покритті, то синів треба обов'язково взяти (щоб ребра між V і синами були покриті
}
}
int main()
{
cin >> n;
for(int i = 1; i < n; i++) {
long long v1,v2;
cin >> v1 >> v2;
A[v1].push_back(v2);
A[v2].push_back(v1);
}
dfs(1,-1); //приймемо за корінь першу вершину
cout<<min(G[1], NG[1])<<endl; //мінімум з варіанті коли корінь берем і не берем
return 0;
}