51nod 1353 树 | 树形DP好题!

题面

切断一棵树的任意条边,这棵树会变成一棵森林。

现要求森林中每棵树的节点个数不小于k,求有多少种切法。

数据范围:\(n \le 2000\)。

题解

//为什么这道题做的人这么少呢……感觉这道题超级经典,非常符合上周末模拟那种树形DP的套路。会做这道题之后,可以想出许多类似的树形DP。

首先状态很好想:\(dp[u][i]\)表示“以u为根的子树中,与u相连的联通块大小是i,剩下的联通块大小均大于k”的方案数。

下面的题解中,我们设要求的是\(dp[u][i]\),父亲是u,儿子是v,sze[i]表示以i为根的子树的大小。

为了方便起见,我们画图说明:

由于u有许多儿子,我们一次只处理一个儿子,处理的时候,就相当于将一堆新的节点(子树v)添加到原有的图。

现在与u相连的联通块大小为i,与j相连的大小为j。显然对于(u, v)之间的这条边,要么切断,要么不切断。上图描述的是切断的情况(这种情况要求j >= k)。显然新的\(dp[u][i] += dp[u][i] * dp[v][j]\)。(注意,等式右边的\(dp[u][i]\)是在联通块中加入子树v之前的答案)。

另一种情况是不切断这条边。如下图:

由于没切断,加入新子树之后,与u相连的联通块的大小由i变成了i + j。

那么显然:\(dp[u][i + j] += dp[u][i] * dp[v][j]\)。(等式右边的dp[u][i]仍是加入子树之前的)。

现在我们连状态转移方程都想好了,写代码是不是就很简单了呢?可能……并不是……(至少对于我来说)

十分需要注意的地方是\(dp[u][i]\)意义的变化。我们必须保证等式右边的\(dp[u][i]\)永远是加入子树之前的答案。例如,对于切断(u, v)这条边的情况,如果我们在u、v没有改变的情况下,直接这样循环:

for r i : 1 -> sze[u]
for j : 1 -> sze[v]
dp[u][i] += dp[u][i] * dp[v][j]

显然是不行的。因为等式右侧的\(dp[u][i]\)一经修改就不是“加入子树v之前的”了。

一种合理的解决方案是用\(dp[v][0]\)表示与v相连的联通块大小大于等于k的所有方案数之和,这样对于切断(u, v)的情况,直接\(dp[u][i] = dp[u][i] * dp[v][0]\)即可,前提是在这一步之前\(dp[u][i]\)没有更新过。所以我们接下来让更小的i中的“不切断的情况”来更新这里的\(dp[u][i]\)。这里直接\(dp[u][i + j] += dp[u][i] * dp[v][j]\)是没有问题的。

啊对了!这个代码看起来是\(O(n^3)\)的啊!

……但其实由于每次两层循环i、j分别枚举了子树u(的已知部分)和子树v的点,时间复杂度增加了sze[u] * sze[v],相当于枚举了每一对点,那么总计每一对点只被枚举了一次。总计\(O(n^2)\)。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ll;
#define INF 0x3f3f3f3f
#define enter putchar('\n')
#define space putchar(' ')
template <class T>
bool read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
else if(c == EOF) return 0;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
return 1;
}
template <class T>
void write(T x){
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 2005, P = 1e9+7;
int n, k, sze[N];
int ecnt, adj[N], nxt[2*N], go[2*N];
ll dp[N][N], ans;
void add(int u, int v){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
}
void dfs(int u, int pre){
dp[u][1] = sze[u] = 1;
for(int v, e = adj[u]; e; e = nxt[e])
if(v = go[e], v != pre){
dfs(v, u);
for(int i = sze[u]; i; i--){
for(int j = 1; j <= sze[v]; j++)
dp[u][i + j] = (dp[u][i + j] + dp[v][j] * dp[u][i]) % P;
dp[u][i] = dp[u][i] * dp[v][0] % P;
}
sze[u] += sze[v];
}
for(int i = k; i <= sze[u]; i++)
dp[u][0] = (dp[u][0] + dp[u][i]) % P;
}
int main(){
read(n), read(k);
for(int i = 1, u, v; i < n; i++)
read(u), read(v), add(u, v), add(v, u);
dfs(1, 0);
for(int i = k; i <= n; i++)
ans = (ans + dp[1][i]) % P;
write(ans), enter;
return 0;
}

51nod 1353 树 | 树形DP经典题!的更多相关文章

  1. POJ 1155 TELE 背包型树形DP 经典题

    由电视台,中转站,和用户的电视组成的体系刚好是一棵树 n个节点,编号分别为1~n,1是电视台中心,2~n-m是中转站,n-m+1~n是用户,1为root 现在节点1准备转播一场比赛,已知从一个节点传送 ...

  2. HDU 2196 Computer 树形DP 经典题

    给出一棵树,边有权值,求出离每一个节点最远的点的距离 树形DP,经典题 本来这道题是无根树,可以随意选择root, 但是根据输入数据的方式,选择root=1明显可以方便很多. 我们先把边权转化为点权, ...

  3. HDU 2196 Computer 树形DP经典题

    链接:http://acm.hdu.edu.cn/showproblem.php? pid=2196 题意:每一个电脑都用线连接到了还有一台电脑,连接用的线有一定的长度,最后把全部电脑连成了一棵树,问 ...

  4. POJ 2486 Apple Tree (树形dp 经典题)

    #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const ...

  5. 【BZOJ-3572】世界树 虚树 + 树形DP

    3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status ...

  6. bzoj 2286(虚树+树形dp) 虚树模板

    树链求并又不会写,学了一发虚树,再也不虚啦~ 2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 5002  Sol ...

  7. 【BZOJ-2286】消耗战 虚树 + 树形DP

    2286: [Sdoi2011消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2120  Solved: 752[Submit][Status] ...

  8. POJ 2342 树形DP入门题

    有一个大学的庆典晚会,想邀请一些在大学任职的人来參加,每一个人有自己的搞笑值,可是如今遇到一个问题就是假设两个人之间有直接的上下级关系,那么他们中仅仅能有一个来參加,求请来一部分人之后,搞笑值的最大是 ...

  9. 洛谷 P1453 城市环路 ( 基环树树形dp )

    题目链接 题目背景 一座城市,往往会被人们划分为几个区域,例如住宅区.商业区.工业区等等.B市就被分为了以下的两个区域--城市中心和城市郊区.在着这两个区域的中间是一条围绕B市的环路,环路之内便是B市 ...

随机推荐

  1. Tetris(俄罗斯方块)

    一天有个小朋友问我OpenGL俄罗斯方块怎么写. 俄罗斯方块分成两部分游戏逻辑和画面渲染. 1. 游戏逻辑 一个简单的俄罗斯方块的逻辑部分需要考虑的情况如下: 1. 方块的表示(坐标, 旋转, 上下左 ...

  2. 在CentOS7上部署PostgreSQL11数据库系统

    在数据库上的选择,也是基于了稳定性为前提.其实选择的范围并不是太大,基本可以选择的范围也就是SQLServer.MySQL.PostgreSQL这三种.SQL Server是微软的商业数据库,无论是性 ...

  3. 苏州地区--校招IT公司

    完整经历了苏州的秋招和春招,在本校和苏州大学跑了许多次的宣讲会,自认为对苏州IT企业的校招有一个充分的认知.原本打算在苏州找一份Java开发的工作,可是发现自己简历连那些公司的简历关都过不去(对双非学 ...

  4. JVM类加载全过程--图解

    JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,下图为实例方法被调用时的JVM内存模型,1~7完整的描述了从类加载开始到方法执行前的预备过程,后面将对每一个步骤进行解释 在我们加载类的过程 ...

  5. 切换nPar或vPar的启动模式

    在EFI Shell中执行vparconfig即可随意切换vpar和npar模式 该命令在\EFI\HPUX目录下 vparconfig        显示当前的模式 vparconfig reboo ...

  6. Spring的Controller映射规则

    URL映射 1) 一般格式@RequestMapping(value=“/test”) 2) 可以使用模板模式映射,@RequestMapping(value=“/test/{userId}”) 3) ...

  7. ME.kkkK

    ME.kkkK 一.预估与实际 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 50 40 • Estima ...

  8. Spring笔记①--helloworld

    Spring Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架,它主要是为了解决企业应用开发的复杂性而诞生的: 目的:解决企业应用开发的复杂性 功能:使用基本的Javabean ...

  9. 四则运算之GUI

    四则运算之GUI Coding克隆地址:https://git.coding.net/lvgx/pair_programming.git   目录: 一.前言 二.计划时间——PSP 三.接口设计 四 ...

  10. iOS开发面试题(中级)

    //想面试的童鞋们来看看自己会多少, 老鸟可以无视直接绕过...1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?与Ex ...