zoukankan      html  css  js  c++  java
  • LOJ 2546 「JSOI2018」潜入行动——树形DP

    题目:https://loj.ac/problem/2546

    dp[ i ][ j ][ 0/1 ][ 0/1 ] 表示 i 子树,用 j 个点,是否用 i , i 是否被覆盖。

    注意 s1<=s0 ,别弄出负角标。

    用 if 判断一下,如果有值再转移,会快非常多。

    复杂度是 O(n*k) 的。证明:https://www.cnblogs.com/cjyyb/p/10416839.html

    先约定如果一个小于 k 的子树和一个大于 k 的子树合并,在小于 k 的子树那里看复杂度。

    1.两个小于 k 的子树 cr 和 v 合并,且合并完之后还是小于 k 的;

      对于 cr 里的每个点,要和 v 的每个点产生贡献。虽然和很多 v 都这样做了,但这些 v 的大小加起来小于 k (因为规定合并完还是小于 k ),所以一个点贡献 O(k) 次。

      如果合并完大于 k ,就在 “一个小于 k 的子树和一个大于 k 的子树合并” 的部分考虑复杂度了。

    2.一个小于 k 的子树 cr 和一个大于 k 的子树 v 合并。

      对于 cr 里的每个点,此时都要进行 O(k) 次贡献。合并完之后 cr 的大小变成大于 k ,所以这种贡献,每个点只会经历一次。

    3.一个大于 k 的子树 cr 和一个大于 k 的子树 v 合并。

      产生 k2 的贡献。如果是两个大小为 k 的子树,合并之后大小变成 2*k ;再合并进来一个大小为 k 的,大小就变成 3*k ;即这种合并最多 ( frac{n}{k} ) 次。

    综上,复杂度是 O(n*k) 的。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    int Mx(int a,int b){return a>b?a:b;}
    int Mn(int a,int b){return a<b?a:b;}
    const int N=1e5+5,M=105,mod=1e9+7;
    int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}
    
    int n,k,hd[N],xnt,to[N<<1],nxt[N<<1];
    int siz[N],dp[N][M][2][2],tp[2][2];
    void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
    void cz(int &x,int y){x=upt(x+y);}
    void dfs(int cr,int fa)
    {
      dp[cr][0][0][0]=dp[cr][1][1][0]=1; siz[cr]=1;
      for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=fa)
          {
        dfs(v,cr);
        for(int s0=Mn(k,siz[cr]+siz[v]);s0>=0;s0--)
          {
            tp[0][0]=tp[0][1]=tp[1][0]=tp[1][1]=0;
            for(int s1=Mx(0,s0-siz[cr]),lm=Mn(s0,Mn(siz[v],k));s1<=lm;s1++)
              {
            int d=s0-s1;
            if(dp[cr][d][0][0])
              {
                cz(tp[0][0],(ll)dp[cr][d][0][0]*dp[v][s1][0][1]%mod);
                cz(tp[0][1],(ll)dp[cr][d][0][0]*dp[v][s1][1][1]%mod);
              }
            if(dp[cr][d][0][1])
              cz(tp[0][1],(ll)dp[cr][d][0][1]*(dp[v][s1][0][1]+dp[v][s1][1][1])%mod);
            if(dp[cr][d][1][0])
              {
                cz(tp[1][0],(ll)dp[cr][d][1][0]*(dp[v][s1][0][0]+dp[v][s1][0][1])%mod);
                cz(tp[1][1],(ll)dp[cr][d][1][0]*(dp[v][s1][1][0]+dp[v][s1][1][1])%mod);
              }
            if(dp[cr][d][1][1])
              {
                cz(tp[1][1],(ll)dp[cr][d][1][1]
                   *((ll)dp[v][s1][0][0]+dp[v][s1][0][1]+dp[v][s1][1][0]+dp[v][s1][1][1])%mod);
              }
              }
            for(int f0=0;f0<=1;f0++)
              for(int f1=0;f1<=1;f1++)
            dp[cr][s0][f0][f1]=tp[f0][f1];
          }
        siz[cr]+=siz[v];
          }
    }
    int main()
    {
      n=rdn();k=rdn();
      for(int i=1,u,v;i<n;i++)
        u=rdn(),v=rdn(),add(u,v),add(v,u);
      dfs(1,0);
      printf("%d
    ",upt(dp[1][k][0][1]+dp[1][k][1][1]));
      return 0;
    }
  • 相关阅读:
    Scala学习笔记--集合类型Queue,Set
    Hadoop学习笔记-HDFS命令
    Spark添加/更改集群节点需要修改的配置文件
    Spark学习笔记-如何运行wordcount(使用jar包)
    Scala学习文档-列表的使用
    Python yaml 使用的包
    Python 通过命令行安装包的时候 pip 提示错误
    Python 注释
    Python 关键字
    Python 数据类型
  • 原文地址:https://www.cnblogs.com/Narh/p/10749068.html
Copyright © 2011-2022 走看看