zoukankan      html  css  js  c++  java
  • codeforce 382 div2 E —— 树状dp

    题意:给一棵n个结点的无根树染色,求使每个结点距离为k的范围内至少有一个被染色的结点的总染色方法数目

    分析:首先我们定义: 对于结点v, 如果存在一个黑色结点u距离v不超过k,则结点v被“控制”

       首先将无根树转换成以1为根的有根树,设dp[v][i]为对于以v为根的子树,距离v最近的黑色结点深度为i时, 该子树中结点全部被控制或者可能在其他子树的影响下被全部控制的染色方法数。

       下面来解释一下, 我们知道,当两个黑色结点距离不超过2*k+1时,其间的所有节点都被控制。那么当 i <= k 时,子树中的结点可以被全部控制, 当 k + 1 <= i <= 2*k+1 时, 子树中的节点不可能自动全被控制,但是可能在该子树根结点染色以及其他子树影响下被全部控制。

       如何计算v的dp值呢,假如v只有一个儿子u, 那么 dp[v][i] = dp[u][i-1]; 如果v有多个儿子,那么首先按上述方法合并v和第一个子树,然后将子树v与其他子树合并。

       那么如何合并两棵子树v, u呢?

       首先考虑v的根结点为白色的状态,可以枚举(i,j)的所有情况,其中 i 是第一棵子树最近黑结点的深度(i>0),j 是第二棵子树最近黑结点的深度,如果i+j <= 2*k,则更新dp[v][min(i, j+1)], 这个状态是节点全部被控制的状态。如果i+j > 2*k, 则此状态子树中节点无法自发地全部被控制,但是可以在其他子树的影响下被全部控制。

       if  i+j <= 2*k , update dp[v][min(i, j+1)]

       else update dp[v][max(i,  j+1)]

       计算完根结点为白色的情况之后,考虑根结点为黑色,那么最近距离v节点不超过2*k+1的情况数目之和就是所求,

                    即 dp[v][0] = ∑ dp[v][i] ( i from 1 to 2*k+1 ) 

       比较难以理解的地方是一个子树全为白色的情况如何进行更新,其实可以这样想,对于每个叶子节点u,假想距离u节点k+1的深度有一个黑色结点,那么对于子树u,dp[u][0] = 1,这是子树完全被控制的情况, dp[u][k+1] = 1, 这是该叶子节点可能在其他子树影响下被控制的情况。这样对于所有节点都可以以上述方式更新。

    ps:此题难点在于状态的设计以及状态的转移方法,需要反复琢磨。

    附上代码:

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #include <vector>
     5 #include <iostream>
     6 
     7 using namespace std;
     8 typedef long long LL;
     9 
    10 #define M 1000000007
    11 #define rep(i, x, n) for(int i = x; i <= (n); i++)
    12 const int maxn = 104;
    13 const int maxk = 20 + 3;
    14 
    15 LL dp[maxn][2*maxk];
    16 LL temp[2*maxk];
    17 
    18 vector<int> G[maxn];
    19 int n, k;
    20 void dfs(int v, int f) {
    21     int len = G[v].size();
    22     if(G[v].size() == 1 && f != 0) { //处理除 1 这个根结点之外的叶子
    23         dp[v][0] = dp[v][k+1] = 1;
    24         return ;
    25     }
    26     int flag = 0;
    27     rep(i, 0, len-1) {
    28         int u = G[v][i];
    29         if(u == f) continue;
    30         dfs(u, v);
    31         if(!flag) {
    32             rep(i, 0, 2*k) dp[v][i+1] = dp[u][i];
    33             flag = 1;
    34         }
    35         else {
    36             rep(i, 0, 2*k+1) temp[i] = dp[v][i], dp[v][i] = 0;
    37             rep(i, 0, 2*k) rep(j, 1, 2*k+1) 
    38                 if(i+j <= 2*k) dp[v][min(j, i+1)] = (dp[v][min(j, i+1)] + temp[j]*dp[u][i]) % M;
    39                 else dp[v][max(j, i+1)] = (dp[v][max(j, i+1)] + temp[j]*dp[u][i]) % M;
    40         }
    41     }    
    42    //计算子树根结点为黑色的情况
    43     rep(i, 1, 2*k+1) dp[v][0] = (dp[v][0] + dp[v][i]) % M;
    44 }
    45 int main(){  
    46     scanf("%d%d", &n, &k);
    47     rep(i, 1, n) G[i].clear();
    48     memset(dp, 0, sizeof dp);
    49     for(int i = 1;i < n; i++){
    50         int a, b;
    51         scanf("%d%d",&a, &b);
    52         G[a].push_back(b);  
    53         G[b].push_back(a);  
    54     }
    55     dfs(1, 0);
    56     LL ans = 0;
    57     for(int i = 0;i <= k; i++)
    58         ans = (ans + dp[1][i])%M;
    59     if(!ans) ans = 1;
    60     cout << ans << endl;  
    61     return 0;  
    62 }  
  • 相关阅读:
    动态链接库DLL
    异常处理
    内存
    线程同步
    线程基础、线程调度
    笔记摘录:进程、作业
    工具DebugView、PCHunter、Procexp、Procmon
    使用Windbg和VMware来搭建调试内核的环境
    C++智能指针
    AndroidEventBus
  • 原文地址:https://www.cnblogs.com/Kiraa/p/6129621.html
Copyright © 2011-2022 走看看