P8127题解
Kao_Potato · · 题解
题目传送门
做完看题解才发现题解的状态设计和转移都好冗余。
题目很简洁明了,很典型的树形DP的题面,考虑状态设计。
首先将无根树提一个根出来以后,若对一个节点进行操作,只会对其本身、父节点和儿子节点造成影响。而我们不希望进行操作时对父节点产生影响,否则不利于DP,所以考虑强制只对当前节点的儿子节点进行操作。
若状态已知则操作产生的影响很容易计算,因此考虑将节点权值放进状态定义中,而我们需知当前节点和儿子节点的权值(儿子的儿子的权值可以根据儿子的状态确定),因此可以得到状态定义。
每个结点对应
-
f_{i,0}$ 表示当前节点 $i$ 权值为 $0$,儿子节点权值为 $0 -
f_{i,1}$ 表示当前节点 $i$ 权值为 $0$,儿子节点权值为 $1 -
f_{i,2}$ 表示当前节点 $i$ 权值为 $1$,儿子节点权值为 $0 -
f_{i,3}$ 表示当前节点 $i$ 权值为 $1$,儿子节点权值为 $1
这里强制定义全部儿子节点的权值相同,且以
接下来考虑转移,转移可以考虑每加进一棵子树对当前节点答案的影响,每个状态都有两种转移方式,一种是从相应的状态直接转移过来,一种是对新加的儿子做一次操作转移过来,则可以写出代码。
#include <cstdio>
#include <iostream>
using namespace std;
const int N=100005,MX=1e9;
int pn,cnt,first[N],v[N],dp[N][4];
struct Bian{
int to,next;
}b[N*2];
void add(int from,int to){
cnt++;
b[cnt]=(Bian){to,first[from]};
first[from]=cnt;
}
void dfs(int now,int f){
if(v[now]==0){
dp[now][2]=dp[now][3]=MX;
}else{
dp[now][0]=dp[now][1]=MX;
}
//初始状态即为单个节点时的状态,此时不用考虑儿子节点,不可能的状态就直接赋为无穷大。
for(int i=first[now];i!=0;i=b[i].next){
if(b[i].to!=f){
dfs(b[i].to,now);
int ls0=dp[now][0],ls1=dp[now][1],ls2=dp[now][2],ls3=dp[now][3];
//一定要将当前的状态记录下来,否则后面的转移会互相影响。
dp[now][0]=min(min(ls0+dp[b[i].to][0],ls2+dp[b[i].to][3]+1),MX);
dp[now][1]=min(min(ls1+dp[b[i].to][2],ls3+dp[b[i].to][1]+1),MX);
dp[now][2]=min(min(ls2+dp[b[i].to][0],ls0+dp[b[i].to][3]+1),MX);
dp[now][3]=min(min(ls3+dp[b[i].to][2],ls1+dp[b[i].to][1]+1),MX);
}
}
}
int main(){
scanf("%d",&pn);
for(int i=1;i<pn;i++){
int from,to;
scanf("%d%d",&from,&to);
add(from,to);
add(to,from);
}
for(int i=1;i<=pn;i++){
scanf("%d",&v[i]);
}
dfs(1,0);
if(min(dp[1][0],dp[1][3]+1)>=MX){
printf("impossible");
}else{
printf("%d",min(dp[1][0],dp[1][3]+1));
}
return 0;
}