前言

模拟赛赛时SubtaskR3没开long long丢了20分。

题意简述

题目描述

对于一棵有根树(设其节点数为 \(n\) ,则节点编号从 \(1\) 至 \(n\) ),如果它满足所有非根节点的编号均比起父亲更大,我们就说它是Y树。

此外,出题人给出了 \(k\) 个整数 \(a_1, \dots, a_k\),并规定,只要一棵有根树存在一个子树包含的节点数恰好为 \(a_1, \dots, a_k\) 中的某一个值,那么它不符合条件;

现给定 \(n,k,a1,\dots,ak\),并额外给定整数 \(L,R\) ,请你对于 \(d = L,L+1,\dots,R\) ,分别求出 \(n\) 个节点的深度为 \(d\) 的符合条件的Y树的数量。

数据范围及提示

\[\begin{matrix}
\text{测试点编号} & | & n\leq & | & k & | & \text{特殊限制} \\
------- & - & -- & - & -- & - &-----\\
1,2,3,4,5,6 & | & 10 & | & <n & | & \text{无} \\
------- & - & -- & - & -- & - &-----\\
7,8,9,10,11 & | & & | & & | & R=3 \\
------- & - & & - & & - &-----\\
12,13,14,15,16 & | & 100 & | &=0 & | & L = n - 2 \\
------- & - & & - & & - &-----\\
17,18,19 & | & & | & & | & & \\
------- & - & & - & -- & - &-----\\
20,21,22 & | & & | & <n & | & \text{无} & \\
------- & - & -- & - & & - & \\
23,24,25 & | & 500 & | & & | & \\
\end{matrix}
\]

对于所有测试点,保证 \(0 \leq k < n \leq 500\)

题解

部分分

首先讲暴力,DFS即可。

首先写一下 \(R \leq 3\) 的部分分。

很多同学赛时试图找规律,但是实际上并没有什么规律,

很显然,当树的高度为2的时候,只有一种情况,就是我们常说的"菊花图"。

那么当树的高度增加到3的时候,显然可以想到一种可行的变换,

我们保留一部分的节点,。

对于 \(L \geq n - 2\) 的部分分。

首先打表找规律,当树高 \(n - 1\) 时,方案数为 \(\frac{n \times (n - 1)}{2} - 2\)。

可以想象为一条链上拆下来一个节点,连接到另一个节点上。

减去的两种方案分别为节点 \(1\) 多计算了一次,节点 \(n\) 不能重新连接到节点 \(n-1\) 上

当树高 \(n - 2\) 时,显然从一条链上拆下来两个点,

我们可以分成两种情况来看

  1. 两个点连在一起
  2. 两个点分开

最后在减去一些零零散散的重复计算部分,就完成了。

代码

警告:以下代码为暴力代码,非常的长,可以跳过

#pragma GCC optimize(3)
#include <cstdio>
#include <cstring>
#include <vector>
#define MOD 998244353 using namespace std; int n, k, L, R;
int a[26]; namespace SubtaskBrute{
vector<int> son[26];
int ban[26], sum[26];
int d[26];
long long ans = 0;
int qry;
bool solve(int u){
int u_s = son[u].size(); sum[u] = 1;
for (int i = 0; i < u_s; ++i){
if (!solve(son[u][i])) return 0;
sum[u] += sum[son[u][i]];
}
return (!ban[sum[u]]);
}
void DFS(int u){
if (u == n + 1){ ans += solve(1); return ; }
for (int i = 1; i < u; ++i){
if (d[i] + 1 > qry) continue;
d[u] = d[i] + 1;
son[i].push_back(u);
DFS(u + 1);
son[i].pop_back();
}
}
int res[26];
void index(){
for (int i = 1; i <= k; ++i) ban[a[i]] = 1;
d[1] = 1;
for (qry = L - 1; qry <= R; ++qry){
if (!qry) res[qry] = 0;
else{ ans = 0; DFS(2); res[qry] = ans; }
}
for (int i = L; i <= R; ++i) printf("%lld ", (res[i] - res[i - 1]) % 998244353);
}
} namespace Subtask1{
int index(int num){
if (num <= 0) return 0;
puts("HJC AK IOI!");
}
} namespace SubtaskR3{
long long f[505][505];
void index(){
f[1][1] = 1;
for (int i = 2; i <= n; ++i)
for (int j = 1; j < i; ++j)
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j] * j) % MOD;
long long ans = -1;
for (int j = 1; j < n; ++j)
ans = (ans + f[n][j]) % MOD;
if (L <= 1) printf("0 ");
if (L <= 2) printf("1 ");
printf("%lld", ans);
}
} namespace SubtaskL2{
inline long long getL1(long long n){
return (((n * (n - 1) >> 1) - 2) % 998244353);
}
void index(){
long long ans = 1 - (n << 1);
for (int i = 2; i < n; ++i)
for (int j = i + 1; j <= n; ++j){
ans += i - 1;
if (i == n - 1) --ans;
ans %= MOD;
}
for (int i = 2; i < n; ++i)
for (int j = i + 1; j <= n; ++j){
if (i == n - 1 && j == n)
ans = (ans + (n - 3) * (n - 3)) % MOD;
else{
ans += (i - 1) * (j - 2) % MOD;
ans %= MOD;
if (j == n - 1) ans -= (i - 1);
else if (j == n) ans -= (i - 1);
}
}
printf("%lld", (ans + MOD) % MOD);
if (R >= n - 1) printf(" %lld", getL1(n));
if (R >= n) printf(" 1");
}
} int main(){
freopen("subtree.in", "r", stdin);
freopen("subtree.out", "w", stdout);
scanf("%d %d", &n, &k);
for (int i = 1; i <= k; ++i) scanf("%d", &a[i]);
scanf("%d %d", &L, &R);
if (n <= 10){ SubtaskBrute::index(); return 0; }
else if (R == 3 && k == 0){ SubtaskR3::index(); return 0; }
else if (L == n - 2 && k == 0){ SubtaskL2::index(); return 0; }
return 0;
}

正解

非常简单的动态规划。

我们定义状态 \(f[i][d]\) 表示大小为 \(i\) ,高度为 \(d\) 的Y树种类数。

我们转移状态的时候考虑以"合并"的方式转移。

为了避免重复计算,我们定义次小节点一定合并到最小节点上(最小节点为树根即1,显然次小节点无论如何都得连接在上面)

避免了重复计算以后,我们枚举合并进来的子树的大小,可以列出如下的式子:

\[f[i][d] = \sum_{j=1}^{i-1} f[i - j][d] \times f[j][d-1] \times \left(\begin{matrix}i - 2 \\ j - 1 \end{matrix}\right)
\]

那么您可能会觉得奇怪,这还没有考虑限制条件呢?

其实我们只需要在DP时,把限制条件所限制的子树大小,DP值清零即可。

代码

#include <cstdio>
#define MOD 998244353 int C[505][505], f[505][505];
bool ban[505]; int main(){
int n, k; scanf("%d %d", &n, &k);
for (int i = 0; i <= n; ++i){
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
for (int i = 1; i <= k; ++i){
int x; scanf("%d", &x);
ban[x] = 1;
}
if (ban[1]){
int L, R; scanf("%d %d", &L, &R);
for (int i = L; i <= R; ++i)
printf("0%c", (i != R ? ' ' : '\n'));
return 0;
}
f[1][1] = 1;
for (int d = 2; d <= n; ++d){
f[1][d] = 1;
for (int i = 2; i <= n; ++i)
for (int j = 1; j < i; ++j)
f[i][d] = (f[i][d] + 1ll * f[i - j][d] * f[j][d - 1] % MOD * C[i - 2][j - 1]) % MOD;
for (int i = 1; i <= n; ++i)
if (ban[i]) f[i][d] = 0;
}
int L, R; scanf("%d %d", &L, &R);
for (int i = L; i <= R; ++i)
printf("%d%c", (f[n][i] - f[n][i - 1] + MOD) % MOD, (i != R ? ' ' : '\n'));
return 0;
}

[HG]子树问题 题解的更多相关文章

  1. [HG]走夜路 题解

    前言 整个机房就我一个人在想动态规划. 想了半天发现一堆性质,结果由于DP中出现折线挂了. 题目描述 某NOIP普及组原题加强版. \(Jim\) 非常怕黑,他有一个手电筒,设手电筒的电量上限为 \( ...

  2. [HG]腿部挂件 题解

    前言 暴力跑的比正解快. 以下暴力(循环展开+fread读入输出优化) #include<cstdio> #pragma GCC optimize(3, "Ofast" ...

  3. 洛谷P1122最大子树和题解

    题目 一道比较好想的树形\(DP\) 完全可以用树形DP的基本思路,递归,然后取最优的方法. \(Code\) #include <iostream> #include <cstri ...

  4. [HG]提高组 题解

    首先很容易想到暴力DP 设状态f[i][j]表示当前放了第i个数,最大的数为j的方案数. 然后根据转移推出实际上是在下图走路的方案数 \[ \left( \left( \begin{matrix} x ...

  5. 【BZOJ】2434: [Noi2011]阿狸的打字机

    题意 给你一些字符串.\(m\)次询问,每一次询问第\(x\)个字符串在\(y\)字符串中出现了多少次.(输入总长$ \le 10^5$, \(M \le 10^5\)) 分析 在ac自动机上,\(x ...

  6. BZOJ 2002: [Hnoi2010]Bounce 弹飞绵羊 LCT

    2002: [Hnoi2010]Bounce 弹飞绵羊 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOn ...

  7. 【洛谷P1272】道路重建

    题目大意:给定一个 N 个节点的树,求至少剪掉多少条边才能使得从树中分离出一个大小为 M 的子树. 题解:考虑树形 dp,定义 \(dp[u][i][t]\) 为以 u 为根节点与前 i 个子节点构成 ...

  8. 【loj#139】树链剖分

    #139. 树链剖分 题目描述 这是一道模板题. 给定一棵 $n$个节点的树,初始时该树的根为 111 号节点,每个节点有一个给定的权值.下面依次进行 $m$ 个操作,操作分为如下五种类型: 换根:将 ...

  9. [洛谷P3833][SHOI2012]魔法树

    题目大意:给一棵树,路径加,子树求和 题解:树剖 卡点:无 C++ Code: #include <cstdio> #include <iostream> #define ma ...

随机推荐

  1. 浅谈Linux cp命令

    Linux 的cp命令 功能: 复制文件或目录说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.若 ...

  2. Spring Boot 五种热部署方式,极速开发就是生产力!

    1.模板热部署 在 Spring Boot 中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭 ...

  3. 客户端实现WebService服务接口

    首先,要获得搭建好的WebService服务的WSDL,如要实现国内手机号码归属地查询WEB服务,其WSDL为:http://ws.webxml.com.cn/WebServices/MobileCo ...

  4. Android渐变色xml配置

    这里渐变色: <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=&quo ...

  5. Redis学习笔记(一)Windows下redis的安装和启动

    在Windows上安装redis 下载地址:https://github.com/microsoftarchive/redis/releases 选择图中红框标出来的下载,解压到磁盘上,文件夹命名为r ...

  6. Vue首页加载过慢 解决方案

    一.什么导致了首页初步加载过慢:app.js文件体积过大 二.解决方法: 1.Vue-router懒加载 vue-router懒加载可以解决首次加载资源过多导致的速度缓慢问题:vue-router支持 ...

  7. nohup重定向到其它的日志文件

    如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件: nohup command > myout.file 2 ...

  8. springboot(十六)-swagger2

    SpringBoot整合Swagger2 相信各位在公司写API文档数量应该不少,当然如果你还处在自己一个人开发前后台的年代,当我没说,如今为了前后台更好的对接,还是为了以后交接方便,都有要求写API ...

  9. SuperMap webgl对接iportal托管的三维服务

    在webgl中对接iportal加密的三维服务时,需要提前注册key值.Cesium.Credential.CREDENTIAL = new Cesium.Credential("你的key ...

  10. java的移位和异或运算

    Java移位运算种类 基础:我们知道在Java中int类型占32位,可以表示一个正数,也可以表示一个负数.正数换算成二进制后的最高位为0,负数的二进制最高为为1 例子: -5换算成二进制后为:1111 ...