题解 P2458 【[SDOI2006]保安站岗】

___new2zy___

2018-08-30 16:46:04

Solution

## 题解 P2458 [SDOI2006]保安站岗 题目传送门: https://www.luogu.org/problemnew/show/P2458 PS:这题真心不错,也算是一道经典的**树形DP**了 ================================================= ### 题目大意: 在**一棵点有点权的树**上,**选择一些点**,这些点能**将所有与它们相连的点覆盖**,**最终将整棵树上的点全部覆盖**,试求最小代价 ### 算法分析: emmm。。。看完题目,一眼就看出是个树形DP ~~(别问我怎么看出来的)(逃~~ 我们发现,要使所有点最终全部被覆盖,那么无非有3种状态: (以下全部都是对于要覆盖任意一个以x为根的子树来说的) (其中我们设y节点为y的儿子,fa为x的父亲) 1.x节点被自己覆盖,即选择x点来覆盖x点 2.x节点被儿子y覆盖,即选择y点来覆盖x点 3.x节点被父亲fa覆盖,即选择fa点来覆盖x点 借此三种状态,我们可以**设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价** 为了方便,我们不妨设这三种情况分别为: 1.f[x][0]---对应上面的1 2.f[x][1]---对应上面的2 3.f[x][2]---对应上面的3 既然是DP,总是有转移方程的,我们想一下dp方程要如何设计 ### 设计状态转移方程: (1):对应上面的1. **f[x][0]=∑ min(f[y][0],f[y][1],f[y][2]) + val[x]** 其中val[x]是选择x点的代价 我们很容易想到,在节点x被选择之后,我们就可以无拘无束了(蛤?),也就是说**对于x儿子节点y的状态可以不去考虑**,因为x节点被选择之后y节点无论如何也会被覆盖到,所以我们**在儿子y的所有状态里取min,累加起来就行了** (2):对应上面的3(先讲3,因为2比较难以理解,放到了后面) **f[x][2]=∑ min(f[y][0],f[y][1])** 为什么3情况对应的转移方程要这样写呢? 我们不妨这样理解,对于x节点我们让它的父亲节点fa覆盖它,那么根据我们的状态设计,**此时必须要满足以x的儿子y为根的子树之中所有点已经被覆盖** 那么**这时就转化为一个子问题**,**要让y子树满足条件**,**只有两种决策:要么y被y的儿子覆盖,要么被y自己覆盖(即选择y节点)**,只需要**在y的这两种状态取min累加就可以了** (3):对应上面的2(DuangDuangDuang 敲黑板划**重点**啦) **f[x][1]=∑ min(f[y][0],f[y][1]),如果选择的全部都是f[y][1],要再加上min(f[y][0]-f[y][1])** 这又是什么意思呢?真是让人~~摸不着头发~~,,,质壁分离(逃 到了这里,我们就要回顾一下我们设计的**dp状态**了: ** 设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价** 先提示一下,如果你理解了下面,那么本题是很简单的。。如果你没理解,就返回到这里再看一遍吧,我就在这里等着你 咳咳。。说正经的。。(逃 对于此时的状态,**f[x][1]代表对于节点x让x被自己的儿子覆盖**,那么和分析(2)一样,都**要先满足此时以y的子树已经满足了条件**,才能进行转移,这就是前面那部分:∑ min(f[y][0],f[y][1])的来历,那么后面那一长串又是怎么回事呢? 我们可以这样理解,此时既然**要保证x点是被自己的儿子覆盖的**,那么如果此时y子树已经满足了全部被覆盖,但是y此时被覆盖的状态却是通过y节点自己的儿子达到的,那么x就没有被儿子y覆盖到,那么我们不妨推广一下,**如果x所有的儿子y所做的决策都不是通过选择y点来满足条件,那么我们就必须要选择x的一个子节点y,其中y满足f[y][0]-f[y][1]最小,并把这个最小的差值累加到f[x][1]中去**,**这样才能使得x点被自己的儿子覆盖**,状态f[x][1]也才能合理地得到转移 好了,如果你还是没有太懂这个(3)的设计过程,请你回到之前再仔细看几遍 如果你已经理解了上面,那么恭喜你这个题,你已经A掉了 因为转移方程既然有了,那么我们就只需要最后的答案了 由于题目中没有说这棵树的根节点是哪个,所以你可以默认1就是根,或者开一个数组在加边的时候记录一下每个点的入度,最后没有入度的点就是根(但好像没有区别,毕竟我A掉了) 最后答案即为min(f[root][0],f[root][1]) 因为根节点没有父亲,所以不需要考虑它的f[root][2]状态 那这样的花。。下面就放一下我丑陋的代码好了(逃 还请dalao们不喜勿喷 PS:代码里也有解释,希望能帮到你更深地理解一下本题 ```cpp #include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> using namespace std; typedef long long ll; const int inf=1e9+7; inline int read() { int p=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();} return f*p;} const int maxn=1503; struct Edge { int from,to; }p[maxn<<1]; int n,cnt,head[maxn<<1],val[maxn]; int f[maxn][4]; //设状态f[x][0],f[x][1],f[x][2]分别表示 //对于x点,被自己覆盖,被自己的儿子覆盖,被自己的父亲覆盖时 //满足以x为根的子树所有点都被覆盖的最小代价 inline void add_edge(int x,int y)//加边 { cnt++; p[cnt].from=head[x]; head[x]=cnt; p[cnt].to=y; } inline void TreeDP(int x,int fa)//树形DP { f[x][0]=val[x];//初值:选择x点 int sum=0,must_need_mincost=inf; for(int i=head[x];i;i=p[i].from) { int y=p[i].to; if(y==fa)continue; TreeDP(y,x); int t=min(f[y][0],f[y][1]); f[x][0]+=min(t,f[y][2]); //自己被自己覆盖:儿子怎么样都行 f[x][2]+=t; //自己被父节点覆盖:儿子必须合法,要么选择儿子,要么是儿子被儿子的儿子覆盖 //以下是对f[x][1]的转移,请好好理解 if(f[y][0]<f[y][1])sum++; //如果选择儿子节点更优,选上,计数器sum++,证明选过f[y][0] else must_need_mincost=min(must_need_mincost,f[y][0]-f[y][1]); //否则记录一个最小的必须支付代价 //因为最后要保证x点被y覆盖,必须要找差值最小的,这样才最优 f[x][1]+=t;//自己被儿子覆盖,那么儿子必须合法 } if(!sum)f[x][1]+=must_need_mincost; //对于f[x][1]转移:如果一个f[y][0]都没选过,那么必须从差值最小的儿子里面选择一个 } int main() { n=read(); for(int i=1;i<=n;i++) { int x=read(); val[x]=read(); int num=read(); while(num>0) { int y=read(); add_edge(x,y); add_edge(y,x); num--; } } TreeDP(1,0); printf("%d",min(f[1][0],f[1][1])); //由于根节点没有父节点,最后答案就是min(f[1][0],f[1][1]) //即1节点被自己覆盖或者被自己的儿子覆盖 return 0; } ``` 好了,到这里就没了。。。 如果有什么不懂的还可以私信我,或者在评论区里留言也行 欢迎各位奆佬指出错误,我会改正的 最后推广一下自己的blog: https://www.luogu.org/blog/new2zy/ 感谢阅读,拜拜~~~ >=<