题解 P2016 战略游戏
Posted justinrochester
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P2016 战略游戏相关的知识,希望对你有一定的参考价值。
解法跟 dalao @real_ljs 类似,但没有用到递归
【分析】
题目相当于需要求覆盖这颗树需要的最小点数
用 (Dp_{i,0/1}) 表示在这棵树中,以 (i) 为根节点的子树在选/不选根节点的情况下,覆盖这棵树所有边需要的最小点数
所以,当不选这个节点 (i) 时,则所有 以其子节点为根节点的子树 都必选根节点
当选择这个节点 (i) 时,它能连接到所有的子节点,所以 以其子节点为根节点的子树 可以选则其根节点,也可以不选
归纳成方程组,可能更容易理解:
(egin{cases}Son_i eq varnothing\\ \\ displaystyle Dp_{i,0}=sum_{jin Son_i}Dp_{j,1}\\ \\displaystyle Dp_{i,1}=1+sum_{jin Son_i}min(Dp_{j,0},Dp_{j,1})end{cases})
其中,(Son_i) 为 (i) 的子节点集合
显然,边界为
(egin{cases}Son_i=varnothing\\Dp_{i,0}=0\\Dp_{i,1}=1end{cases})
如果定义 (Son_i=varnothing) 时 (displaystyle sum_{jin Son_i}Dp_{j,1}=sum_{jin Son_i}min(Dp_{j,0},Dp_{j,1})=0)
递推式及其边界便能写在一起了:
(egin{cases}displaystyle Dp_{i,0}=0+sum_{jin Son_i}Dp_{j,1}\\ \\displaystyle Dp_{i,1}=1+sum_{jin Son_i}min(Dp_{j,0},Dp_{j,1})end{cases})
答案即为 (min(Dp_{root,0},Dp_{root,1}))
很显然,根据树形dp的特点,我们要从叶子开始回溯回去,一般用dfs,或者说是递归,来辅助进行
但是,毕竟本人不喜欢递归,所以想到了一个方法可以避免使用递归
我们来想想,这题的树形dp只要满足子节点的dp在父节点之前完成即可
那我们可以先从根节点开始bfs一下,把树压成一条链,然后再把bfs访问的节点倒序完成dp即可
因为bfs过程中,子节点一定由父节点拓展而来,故子节点在bfs中一定在父节点之后访问到
dp的时候我们倒叙访问,就一定能保证子节点先完成dp,再由父节点完成dp
具体实现方法也不难,最后倒叙访问实际上就是一个栈,我们让这个数组在bfs的时候做队列,dp的时候再当作栈即可
考虑一下时间复杂度:
读入各个节点需要 (O(n)),读入他们的子节点数也需要 (O(n))
读入子节点为 (displaystyle sum_{i=1}^n Card(Son_i)=n-1) ,也为 (O(n)) 的
找根节点、bfs为 (O(n))
dp的内层次为 (O( Card(Son_i) ))
dp的复杂度为 (displaystyle O( sum_{i=1}^n[1+Card(Son_i)] )=O( displaystyle n+sum_{i=1}^nCard(Son_i) )=O(n+n-1))
因此,dp的复杂度为 (O(n))
很显然,总复杂度也为 (O(n))
简直快得飞起
【代码】
那本蒟蒻就放 我码风极丑的 代码了
#include<cstdio>
#include<cstring>
using namespace std;
#define f(a,b,c,d) for(register int a=b,c=d;a<=c;a++)
#define g(a,b,c,d) for(register int a=b,c=d;a>=c;a--)
const int MAXN=1510;
typedef int i32;
typedef unsigned int u32;
typedef long long int i64;
typedef unsigned long long int u64;
typedef i32 ar[MAXN];
typedef i32 mt[MAXN][MAXN];
#define cls(s) memset(s,0,sizeof(s))
inline i32 min(int a,int b) { return (a<b)?a:b; }
//条件反射般的一堆定义
namespace IO{
// #define LOCAL
#ifdef LOCAL
inline char gc() { return getchar(); }
#else
inline char gc(){
static char s[1<<20|1]={0},*p1=s,*p2=s;
return (p1==p2)&&(p2=(p1=s)+fread(s,1,1<<20,stdin),p1==p2)?EOF:*(p1++);
}
#endif
inline i32 read(){
register int ans=0;register char c=gc();register bool neg=0;
while(c<48||c>57) neg^=!(c^'-'),c=gc();
while(c>=48&&c<=57) ans=(ans<<3)+(ans<<1)+(c^48),c=gc();
return neg?-ans:ans;
}
char Output_Ans[1<<20|1]={0},*Output_Cur=Output_Ans;
inline void output() { Output_Cur-=fwrite(Output_Ans,1,Output_Cur-Output_Ans,stdout); }
inline void print(char c){
if(Output_Cur-Output_Ans+1>>20) output();
*(Output_Cur++)=c;
}
inline void print(i32 ans){
char s[20]={0};
if(Output_Cur-Output_Ans+sprintf(s,"%d",ans)>>20) output();
Output_Cur+=sprintf(Output_Cur,"%d",ans);
}
}
using namespace IO;
//条件反射般的读入输出优化
i32 d_N,d_Cur;
ar ar_d_Stk;
mt mt_d_Son;
inline void pre(){
d_N=read();
bool b_Notroot[MAXN]={0};
f(i,1,I,d_N){
i32 d_Pos=read();
mt_d_Son[d_Pos][0]=read();
//第一位存子节点数
f(j,1,J,mt_d_Son[d_Pos][0])
mt_d_Son[d_Pos][j]=read(),b_Notroot[ mt_d_Son[d_Pos][j] ]=1;
}
//读入,并同时判定根节点
ar_d_Stk[d_Cur]=0;
while(b_Notroot[ ar_d_Stk[d_Cur] ]) ar_d_Stk[d_Cur]++;
d_Cur++;
//寻找根节点,放入队列头
i32 d_Head=0;
while(d_Head<d_Cur){
i32 d_Pos=ar_d_Stk[d_Head++];
f(i,1,I,mt_d_Son[d_Pos][0])
ar_d_Stk[d_Cur++]=mt_d_Son[d_Pos][i];
}
//bfs压树为链
}
int main(){
pre();
i32 mt_d_Dp[MAXN][2]={0};
while(d_Cur--){
i32 i=ar_d_Stk[d_Cur];//取出栈顶
mt_d_Dp[i][0]=0,mt_d_Dp[i][1]=1;
if(mt_d_Son[i][0])
f(j,1,J,mt_d_Son[i][0]){
i32 d_Kid=mt_d_Son[i][j];
mt_d_Dp[i][0]+=mt_d_Dp[d_Kid][1];
mt_d_Dp[i][1]+=min(mt_d_Dp[d_Kid][0],mt_d_Dp[d_Kid][1]);
}
}
print( min(mt_d_Dp[ ar_d_Stk[d_Cur] ][0],mt_d_Dp[ ar_d_Stk[d_Cur] ][1]) );
output();
return 0;
}
最后安利一下 本蒟蒻的博客
以上是关于题解 P2016 战略游戏的主要内容,如果未能解决你的问题,请参考以下文章