link:https://codeforces.com/gym/104880
題意:給一棵樹,每次操作選一個還存在的點,將點和所有與其相連的邊刪去。要求 操作次數+剩餘邊數
的最小值。
複雜度不重要,當做要線性的就好。這題本身的想法很自然:剩餘邊數=n-1-刪去的邊數,所以只要每當還存在某個點,有連邊的話,多操作一次會讓次數+1,而刪去邊數至少-1,所以最終狀態必然是所有邊都被刪掉。
那現在就變成求一個點集,使得刪掉這些點之後就能刪去所有邊,這就是最小點覆蓋!而對於二分圖來說(樹自然是二分圖了),最小點覆蓋=點數-最大獨立集,直接去dp算最大獨立集即可
[!Note]
點覆蓋就是用點去覆蓋邊的點集,類似地邊覆蓋就是用邊覆蓋點的邊集。
而獨立集則是兩兩之間不連邊的點集。
路徑覆蓋是覆蓋所有點的路徑集合。
對於二分圖來說,這幾者間有很深刻的聯絡:
- 最大流=最小割=最大匹配
- 最小點覆蓋=最大匹配=n-最大獨立集
- 最小邊覆蓋=n-最大匹配=n-最小點覆蓋=最大獨立集
- 最小路徑覆蓋:拆點,\(V\) 拆成 \(V_x\to V_y\) 後,=n-新圖的最大匹配
這裡只是順帶回顧一下,這裡如果能馬上意識到算最大獨立集的話,就是一個很簡單的樹上dp的做法
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N=5e5+5;
int n,f[N][2];
vector<vector<int>> G;
void dfs(int x,int fa){
f[x][0]=0;f[x][1]=1;
for(auto to:G[x])if(to!=fa){
dfs(to,x);
f[x][0]+=max(f[to][0],f[to][1]);
f[x][1]+=f[to][0];
}
}
int main(){
fastio;
cin>>n;
G=vector<vector<int>>(n+1);
rep(i,1,n-1){
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,-1);
cout<<n-max(f[1][0],f[1][1]);
return 0;
}