前言

模拟赛赛时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. java中的12种锁

    java中很多地方会涉及到锁,比如java代码并发场景,DB中的并发场景,分布式中的锁....你知道几种呢?下面来看看常见的11种锁 1. 乐观锁/悲观锁 这两个概念是人们对java中各种锁总结提出的 ...

  2. Java中的异常处理try catch(第八周课堂示例总结)

    异常处理 使用Java异常处理机制: 把可能会发生错误的代码放进try语句块中. 当程序检测到出现了一个错误时会抛出一个异常对象. 异常处理代码会捕获并处理这个错误. catch语句块中的代码用于处理 ...

  3. Luogu P4902 乘积

    题目 我们要求的是 \[ \prod\limits_{i=a}^b\prod\limits_{j=1}^i(\frac ij)^{\lfloor\frac ij\rfloor} \] 先把它拆开 \[ ...

  4. IDEA 修改JavaWeb的访问路径

    问题描述        对于我这个刚刚使用IDEA不久的新手来说,能够正常运行就不错了,不过到了后面,可能会觉得IDEA给你分配的默认访问路径很不顺手,比如访问的时候需要通过: http://loca ...

  5. 02:linux常用命令

    1.1 linux查看系统基本参数常用命令 1.查看磁盘 [root@linux-node1 ~]# df -hl Filesystem Size Used Avail Use% Mounted on ...

  6. mysql-1.1基础

    笔记内容:mysql基础,创建数据库,创建表,操作数据表,操作数据,简单查询,条件查询,排序,分组,聚合,连接查询(等值连接,内连接,外链接),子查询 自己提示:脑图笔记存于网盘中  右键:新标签页打 ...

  7. redis 学习(12)-- redis 发布订阅

    redis 发布订阅 发布订阅模式中的角色 发布者(publisher) 订阅者(subscriber) 频道(channel) 如图所示: 发布者发布消息到频道,订阅了频道的订阅者可以收到消息,订阅 ...

  8. redis 学习(6)-- 集合类型

    redis 学习(6)-- 集合类型 set 结构 无序 无重复 集合间操作 set 集合内操作 命令 含义 sadd key memebr1 [member2...] 向集合中添加一个或多个成员 s ...

  9. git bash中不能显示中文

    git bash中不能显示中文 问题描述:当使用git log查看提交日志时,中文字符不能正常显示问题 1.首先把git的配置改一下 git config --global core.quotepat ...

  10. 对ArrayList中的Person对象按照先年龄从大到小,相同年龄的再按照姓名(姓名是英文的)的字母顺序进行排序.

    ListDemo2.java ----------------- package com.fs.test; import java.util.ArrayList; import java.util.C ...