题解 | #Strategic game#
Strategic game
https://ac.nowcoder.com/acm/problem/51222
Strategic game
题意:
给你一棵树,已知于某点放一个士兵,则与之相连的边就都能被“看守”,问使所有边都能有人看守至少要放置多少士兵。(本题为多组输入)
【与“没有上司的舞会”神似,经典树形dp】
思路:
树形dp为类似“树的后序遍历”的dfs, 由叶结点向根结点转移。
一条边要被看守,则其两端点至少要有一点有士兵。设 f[i] 为以 i 为根节点的子树所需最少士兵数,i 点有两种状态:放或不放。对于一条边,若 i 放置士兵,则该边所连的 i 的子结点可放可不放,取最小的一种;若 i 不放,则另一点必须放(否则这条边就无人看守了)。f[i][1]为放置,f[i][0]不放。则状态转移方程:
f[i][1]=1+Σ(max(f[j][1],f[j][0]));
f[i][0]=Σ(f[j][1]);
代码的实现上有两种写法:
一、单向边,求出根(root)再从root开始dfs
#include<algorithm>
#include<vector>
using namespace std;
const int M=1510;
int n,f[M][2];
vector<int> son[M];
void dfs(int i){
f[i][1]=1; //保证边界正确,就算为叶结点,后面的不执行,也正确。
for(int x=0;x<son[i].size();x++){
int j=son[i][x];
dfs(j);
f[i][1] += min(f[j][0],f[j][1]);
f[i][0] += f[j][1];
}
}
int main(){
while(~scanf("%d",&n)){
for(int i=0;i<n;i++){
f[i][1]=f[i][0]=0;
son[i].clear();
}
int root=n*(n-1)/2; //先把所有点的和求出来
for(int i=0,x,m;i<n;i++){
scanf("%d:(%d)",&x,&m);
for(int j=0,y;j<m;j++){
scanf("%d",&y);//y是x的子节点
root -= y; //再依次减去子节点,最后剩下的就是根结点
son[x].push_back(y);
}
}
dfs(root);
printf("%d\n",min(f[root][1],f[root][0]));
}
return 0;
}
二、存双向边,可以任意点为根结点开始搜索
#include<algorithm>
#include<vector>
using namespace std;
const int M=1510;
int n,f[M][2];
vector<int> son[M];
void dfs(int i,int fa){//定义一个fa(父亲)结点
f[i][1]=1;
for(int x=0;x<son[i].size();x++){
int j=son[i][x];
if(j==fa)continue; //注意若与fa相等则不搜
dfs(j,i);
f[i][1] += min(f[j][0],f[j][1]);
f[i][0] += f[j][1];
}
}
int main(){
while(~scanf("%d",&n)){//多组输入
for(int i=0;i<n;i++){
f[i][1]=f[i][0]=0;
son[i].clear(); //清空
}
for(int i=0,x,m;i<n;i++){
scanf("%d:(%d)",&x,&m);
for(int j=0,y;j<m;j++){
scanf("%d",&y);
son[x].push_back(y);
son[y].push_back(x);
}
}
dfs(0,-1); //这里以0为根结点搜
printf("%d\n",min(f[0][1],f[0][0]));
}
return 0;
}