zoukankan      html  css  js  c++  java
  • 【纪中集训2019.08.25】【JZOJ6371】树

    题目链接

    题意:

      给定一棵$n$个节点的无根树,可以对边黑白染色。求对于每个点有多少种染色方案,使得它到任意点的路径上至多经过一条黑边。对$10^9+7$取模。

      $1le n le 10^5$

    分析:

      可以看出,这题就是把每个点做根的情况都求一遍。

      先来看链的情况:以$i$号点做根,那么左侧的边总共只能有一条染黑;右侧同理。由乘法原理可得答案为$i imes (n-i+1)$。

      考虑树形DP。假设确定$1$号点为根节点,设$f_i$表示由下至上$i$点及其子树的答案,那么很容易得到$$f_i=prodlimits_{jin son_i} ( f_j+1 )$$

      算法是儿子到父亲的这条边染不染黑。染了那么整棵子树都不能再染,不染就是直接传子树答案。兄弟之间对父亲的贡献互不干扰,所以要乘起来。

      现在来考虑怎么换根。

      发现我们的答案和深度无关,同一棵子树的贡献总是一定的,考虑记忆化。

      但是换根之后树的形态发生变化,不能用点代表子树。

      发现点不能代表子树的原因是遍历子树的方向会有不同。即:可以用不同的边进入同一点。

      那么就可以对边记忆化。完成!

    实现(100分):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define IL inline
    using namespace std;
    typedef long long LL;
    const int N=1e5;
    const int mod=1e9+7;
    
        int n,p[N+3];
    
    struct Edge{
        int to,nxt;
        
    }e[N*2+3];
        int h[N+3],top;
        
    IL void adde(int u,int v){
        top++;
        e[top].to=v;
        e[top].nxt=h[u];
        h[u]=top;
        
    }
        
        bool vis[N+3],mrk[N*2+3];
        LL f[N*2+3];
    LL dfs(int u){
        LL ret=1LL;
        for(int i=h[u];~i;i=e[i].nxt){
            int v=e[i].to;
            if(vis[v])
                continue;
            
            vis[v]=true;
            if(!mrk[i]){
                mrk[i]=true;
                f[i]=dfs(v);
                
            }
            ret=ret*(f[i]+1)%mod;
            
        }
        
        return ret;
        
    }
    
    int main(){
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
    
        scanf("%d",&n);
        top=-1;
        memset(h,-1,sizeof h);
        for(int i=2;i<=n;i++){
            scanf("%d",&p[i]);
            adde(i,p[i]);
            adde(p[i],i);
            
        }
        
        bool flag3=true;
        for(int i=2;i<=n&&flag3;i++)
        if(p[i]!=i-1)
            flag3=false;
        
        if(flag3){
            for(int i=1;i<=n;i++)
                printf("%lld ",(LL)i*(n-i+1)%mod);
            return 0;
            
        }
        
        memset(mrk,0,sizeof mrk);
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof vis);
            vis[i]=true;
            printf("%lld ",dfs(i));
            
        }
        
        return 0;
    
    }
    View Code

    小结:

      没什么好说的。认真水题吧。

  • 相关阅读:
    centos 系统时间设置
    centos6 centos7 配置开机启动服务
    centos6.9 samba配置
    vmware异常关闭后导致虚拟机无法打开问题解决办法
    try using -rpath or -rpath-link
    ZR#988
    提高十连测day3
    Atcoder ABC 141
    ZR#957
    ST表
  • 原文地址:https://www.cnblogs.com/Hansue/p/11408033.html
Copyright © 2011-2022 走看看