该题是点的最小覆盖集问题。
题意:求覆盖一个树所有的点,需要覆盖最小的次数,覆盖一个点需要耗费一次次数,覆盖完以后,那么相邻的点也被覆盖。
对于无向树,直接随便找一个结点作为根,然后把整棵树支起来,就有了根和叶子!
需要一点想象力,反正无向树怎么旋转都可以。
那么寻找当前点的状态,如果一个树都被覆盖,那么这一个点,肯定要么是自己覆盖的,要么是父亲覆盖的,要么是儿子覆盖的,就找到了三种状态。
状态1:自己覆盖
那么对于自己的儿子而言,可以覆盖也可以不覆盖(求min就好了)
状态2:儿子覆盖
那么只要有至少有一个儿子是自己覆盖了的就好了
状态3:父亲覆盖
那么对于自己的儿子,可以是他本身覆盖,也可以是儿子的儿子覆盖。
对于特殊的点,要特殊考虑,有以下两种情况:
1.当前点没有父亲(根结点),那么该点的状态3不存在
2.当前点没有孩子(叶子结点),那么该点状态2不存在
状态转移方程:
dp[root][0]=1+min(dp[son][0],dp[son][1],dp[son][2])
自己覆盖的时候,孩子们的状态无所谓
如果root点有孩子:
dp[root][1]=min(dp[son][0],dp[son][1])+inc(if(dp[son][0]>dp[son][1])) inc=min(inc,(dp[son][0]>dp[son][1])))
如果root点没有孩子:
dp[root][1]=INF
如果root点有父亲:
dp[root][2]= min(dp[to][0],dp[to][1])
如果root点没有父亲:
dp[root][2]=INF
有了状态转移方程,只要通过dfs搜索的时候把状态转移方程带入即可求解。
以下是代码:
#include <bits/stdc++.h>
using namespace std;
const int N=10100;
const int INF=0x3f3f3f3f;
vector <int> G[N];
int dp[N][3];//0表示该点覆盖 1表示该点儿子覆盖 2表示该点父亲覆盖
void dfs(int root,int pre)
{
bool haveson=false;
bool coverson=false;
int inc=0x3f3f3f3f;
for(int i=0;i<G[root].size();i++)
{
int to=G[root][i];
if(to==pre) continue;
haveson=true;
dfs(to,root);
dp[root][0]+=min(dp[to][0],min(dp[to][1],dp[to][2]));
if(root==1) dp[root][2]=INF;
else dp[root][2]+=min(dp[to][0],dp[to][1]);
inc=min(inc,dp[to][0]-dp[to][1]);
if(dp[to][0]<=dp[to][1])
{
coverson=true;
dp[root][1]+=dp[to][0];
}
else
{
dp[root][1]+=dp[to][1];
}
}
if(!haveson) dp[root][1]=INF;
else if(!coverson) dp[root][1]+=inc;
dp[root][0]+=1;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
printf("%d\n",min(dp[1][0],min(dp[1][1],dp[1][2])));
}


京公网安备 11010502036488号