zoukankan      html  css  js  c++  java
  • 题解 P2016 【战略游戏】

    题目

    解法跟 dalao @real_ljs 类似,但没有用到递归


    【分析】

    题目相当于需要求覆盖这颗树需要的最小点数

    \(Dp_{i,0/1}\) 表示在这棵树中,\(i\) 为根节点的子树选/不选根节点的情况下,覆盖这棵树所有边需要的最小点数

    所以,当不选这个节点 \(i\) 时,则所有 以其子节点为根节点的子树 都必选根节点

    当选择这个节点 \(i\) 时,它能连接到所有的子节点,所以 以其子节点为根节点的子树 可以选则其根节点,也可以不选

    归纳成方程组,可能更容易理解:

    \(\begin{cases}Son_i\neq \varnothing\\\ \\ \displaystyle Dp_{i,0}=\sum_{j\in Son_i}Dp_{j,1}\\\ \\\displaystyle Dp_{i,1}=1+\sum_{j\in Son_i}min(Dp_{j,0},Dp_{j,1})\end{cases}\)

    其中,\(Son_i\)\(i\) 的子节点集合

    显然,边界为

    \(\begin{cases}Son_i=\varnothing\\Dp_{i,0}=0\\Dp_{i,1}=1\end{cases}\)

    如果定义 \(Son_i=\varnothing\)\(\displaystyle \sum_{j\in Son_i}Dp_{j,1}=\sum_{j\in Son_i}min(Dp_{j,0},Dp_{j,1})=0\)

    递推式及其边界便能写在一起了:

    \(\begin{cases}\displaystyle Dp_{i,0}=0+\sum_{j\in Son_i}Dp_{j,1}\\\ \\\displaystyle Dp_{i,1}=1+\sum_{j\in 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;
    }
    

    最后安利一下 本蒟蒻的博客

  • 相关阅读:
    瓜子二手车直卖网面试经历
    189. Rotate Array
    183. Customers Who Never Order
    182. Duplicate Emails
    文本的样式
    字体font
    定位position
    float引起的高度塌陷问题
    盒子大小 轮廓阴影 圆角
    文档流 颜色单位
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/12268325.html
Copyright © 2011-2022 走看看