题意
有一个无根树,要选m个点,使得另外的n-m个点都能与这m个点相连,且只能连一个,求最小的m。
题解
经典的覆盖问题,覆盖距离为1,有贪心的解法,也有dp的解法。
这里讲讲dp的解法。
dp[i][0]
:选i为服务器。dp[i][1]
:i不是服务器,但i的父亲是。dp[i][2]
:i和i的父亲都不是服务器。但儿子中有一个是服务器。
转移方程:
以下v都代表u的儿子。
dp[u][0] += min{dp[v][0], dp[v][1])
如果u是服务器,那么v可以满足1,2。dp[u][1] += dp[v][2]
如果u不是服务器,但父亲是,那么儿子就都不是服务器。这里我有点疑惑,为什么儿子不能也是服务器,仔细读题发现,一个客户端只能连接一个服务器!但其实去掉这个条件,也是没问题的,因为最优的状态一定是中间放服务器,而不是两端放服务器,所以如果dp[v][2]
得不出最优解,那么即使v放服务器也是没有效果的。dp[u][2] = min(dp[u][1]-dp[v][2]+dp[v][0])
如果u不是服务器且父亲也不是,那么儿子里面肯定要有一个是服务器,这里可以用前面得到的状态dp[u][1]
代表儿子全都不是服务器的情况,那么根据容斥原理全都不是服务器-v不是服务器+v是服务器
即得到了答案。
理解的还不够深刻,有错希望有大佬指出!
代码
#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 10000+5;
int n;
vector<int> G[maxn];
void add(int u, int v) {
G[u].push_back(v);
}
int dp[maxn][3];
void dfs(int u, int fa) {
dp[u][0] = 1;
dp[u][1] = 0;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(v == fa) continue;
dfs(v,u);
dp[u][0] += min(dp[v][0],dp[v][1]);
dp[u][1] += dp[v][2];
}
dp[u][2] = 1000000;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(v == fa) continue;
dp[u][2] = min(dp[u][2], dp[u][1]-dp[v][2]+dp[v][0]);
}
}
int main() {
while(scanf("%d", &n) && n != -1) {
for(int i = 0; i <= n; ++i) G[i].clear();
int u,v;
for(int i = 0; i < n-1; ++i) {
scanf("%d%d", &u ,&v);
add(u,v);
add(v,u);
}
dfs(1,-1);
scanf("%d", &n);
cout << min(dp[1][0],dp[1][2]) << endl;
if(n == -1) break;
}
return 0;
}