[HG]子树问题 题解
前言
模拟赛赛时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树的数量。
数据范围及提示
\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\) 时,显然从一条链上拆下来两个点,
我们可以分成两种情况来看
- 两个点连在一起
- 两个点分开
最后在减去一些零零散散的重复计算部分,就完成了。
代码
警告:以下代码为暴力代码,非常的长,可以跳过
#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,显然次小节点无论如何都得连接在上面)
避免了重复计算以后,我们枚举合并进来的子树的大小,可以列出如下的式子:
\]
那么您可能会觉得奇怪,这还没有考虑限制条件呢?
其实我们只需要在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]子树问题 题解的更多相关文章
- [HG]走夜路 题解
前言 整个机房就我一个人在想动态规划. 想了半天发现一堆性质,结果由于DP中出现折线挂了. 题目描述 某NOIP普及组原题加强版. \(Jim\) 非常怕黑,他有一个手电筒,设手电筒的电量上限为 \( ...
- [HG]腿部挂件 题解
前言 暴力跑的比正解快. 以下暴力(循环展开+fread读入输出优化) #include<cstdio> #pragma GCC optimize(3, "Ofast" ...
- 洛谷P1122最大子树和题解
题目 一道比较好想的树形\(DP\) 完全可以用树形DP的基本思路,递归,然后取最优的方法. \(Code\) #include <iostream> #include <cstri ...
- [HG]提高组 题解
首先很容易想到暴力DP 设状态f[i][j]表示当前放了第i个数,最大的数为j的方案数. 然后根据转移推出实际上是在下图走路的方案数 \[ \left( \left( \begin{matrix} x ...
- 【BZOJ】2434: [Noi2011]阿狸的打字机
题意 给你一些字符串.\(m\)次询问,每一次询问第\(x\)个字符串在\(y\)字符串中出现了多少次.(输入总长$ \le 10^5$, \(M \le 10^5\)) 分析 在ac自动机上,\(x ...
- BZOJ 2002: [Hnoi2010]Bounce 弹飞绵羊 LCT
2002: [Hnoi2010]Bounce 弹飞绵羊 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOn ...
- 【洛谷P1272】道路重建
题目大意:给定一个 N 个节点的树,求至少剪掉多少条边才能使得从树中分离出一个大小为 M 的子树. 题解:考虑树形 dp,定义 \(dp[u][i][t]\) 为以 u 为根节点与前 i 个子节点构成 ...
- 【loj#139】树链剖分
#139. 树链剖分 题目描述 这是一道模板题. 给定一棵 $n$个节点的树,初始时该树的根为 111 号节点,每个节点有一个给定的权值.下面依次进行 $m$ 个操作,操作分为如下五种类型: 换根:将 ...
- [洛谷P3833][SHOI2012]魔法树
题目大意:给一棵树,路径加,子树求和 题解:树剖 卡点:无 C++ Code: #include <cstdio> #include <iostream> #define ma ...
随机推荐
- 浅谈Linux cp命令
Linux 的cp命令 功能: 复制文件或目录说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.若 ...
- Spring Boot 五种热部署方式,极速开发就是生产力!
1.模板热部署 在 Spring Boot 中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭 ...
- 客户端实现WebService服务接口
首先,要获得搭建好的WebService服务的WSDL,如要实现国内手机号码归属地查询WEB服务,其WSDL为:http://ws.webxml.com.cn/WebServices/MobileCo ...
- Android渐变色xml配置
这里渐变色: <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=&quo ...
- Redis学习笔记(一)Windows下redis的安装和启动
在Windows上安装redis 下载地址:https://github.com/microsoftarchive/redis/releases 选择图中红框标出来的下载,解压到磁盘上,文件夹命名为r ...
- Vue首页加载过慢 解决方案
一.什么导致了首页初步加载过慢:app.js文件体积过大 二.解决方法: 1.Vue-router懒加载 vue-router懒加载可以解决首次加载资源过多导致的速度缓慢问题:vue-router支持 ...
- nohup重定向到其它的日志文件
如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件: nohup command > myout.file 2 ...
- springboot(十六)-swagger2
SpringBoot整合Swagger2 相信各位在公司写API文档数量应该不少,当然如果你还处在自己一个人开发前后台的年代,当我没说,如今为了前后台更好的对接,还是为了以后交接方便,都有要求写API ...
- SuperMap webgl对接iportal托管的三维服务
在webgl中对接iportal加密的三维服务时,需要提前注册key值.Cesium.Credential.CREDENTIAL = new Cesium.Credential("你的key ...
- java的移位和异或运算
Java移位运算种类 基础:我们知道在Java中int类型占32位,可以表示一个正数,也可以表示一个负数.正数换算成二进制后的最高位为0,负数的二进制最高为为1 例子: -5换算成二进制后为:1111 ...