题目传送门
其实这道题跟我的上一篇的三道题都挺像的
尤其是树的最大独立集
但这道题还是有本质上的差别
那三道题都是要尽量多
而这道题要尽量少
所以区别还是很大的
而且这道题的dp的定义以及状态转移方程都与那三题大不一样
首先,我们定义三种不同状态的 dp ::
- dp [ i ] [ 0 ] ::i 节点的子树和 i 节点被全覆盖,i 节点上有塔
- dp [ i ] [ 1 ] ::i 节点的子树和 i 节点被全覆盖,i 节点无塔
- dp [ i ] [ 2 ] ::i 节点的子树被全覆盖,但 i 节点未被覆盖
对于这三种状态,我们分别可以得到以下三种状态转移方程::
第一种情况,他的儿子节点的每一种状态都可以转移上来,再加上他本身再架设一个
这个是最复杂的一种了。因为如果按定义来看,他至少有一个子节点是有塔的,所以。。。自己画图理解一下
P.S. sum表示覆盖子树的最小值
第三种状态只能由第二个状态转移过来
还有就是这也是一棵无根树,需要自己定义根
(其实这个好像没有什么用,也不需要任何处理,随便从哪个节点开始都没问题)
所以我们可以随便从一个节点开始,然后dfs就行了
代码::
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
using namespace std;
inline void read(int &x) {
x=0;
int f=1;
char s=getchar();
while(s<'0'||s>'9') {
if(s=='-')
f=-1;
s=getchar();
}
while(s>='0'&&s<='9') {
x=x*10+s-48;
s=getchar();
}
x*=f;
}
inline void pr(int x) {
if(x<0) {
putchar('-');
x=-x;
}
if(x>9)
pr(x/10);
putchar(x%10+48);
}//快读快输不解释
vector<int>G[10005];
int n,k,a,b,dp[10005][3];
void dfs(int x,int fa) {
dp[x][0]=1;//本身
dp[x][1]=1000000;//这里不要开得过大,不然一不小心就爆int然后变成负数接而就炸了
int sum=0;//按上面说的累和
int o=G[x].size();
for(int i=0;i<o;i++) {
int v=G[x][i];
if(v==fa)//父节点
continue;
dfs(v,x);//从叶子节点开始逆推上来
dp[x][2]+=dp[v][1];//按上面说的状态转移方程推过来
dp[x][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));//同上
sum=sum+min(dp[v][0],dp[v][1]);//同上
}
if(o==1&&x!=1)//只有一个儿子&&非根节点
return;
for(int i=0;i<o;i++) {
int v=G[x][i];
if(v==fa)
continue;
dp[x][1]=min(dp[x][1],dp[v][0]+sum-min(dp[v][0],dp[v][1]));//同上上
}
}
int main() {
read(n);
for(int i=1;i<n;i++)
read(a),read(b),G[a].push_back(b),G[b].push_back(a);
dfs(1,0);
pr(min(dp[1][0],dp[1][1]));//读入+dfs+输出
}
大概就是这样,不懂的可以评论讨论
更多作者博客在这里