博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UOJ#424. 【集训队作业2018】count
阅读量:6673 次
发布时间:2019-06-25

本文共 2346 字,大约阅读时间需要 7 分钟。

UOJ#424. 【集训队作业2018】count

如果一个序列满足序列长度为\(n\),序列中的每个数都是\(1\)\(m\)内的整数,且所有\(1\)\(m\)内的整数都在序列中出现过,则称这是一个挺好序列。

对于一个序列\(A\),记\(fA(l,r)\)\(A\)的第\(l\)个到第\(r\)个数中最大值的下标(如果有多个最大值,取下标最小的)。

两个序列\(A\)\(B\)同构,当且仅当\(A\)\(B\)长度相等,且对于任意\(i≤j\),均有\(fA(i,j)=fB(i,j)\)

给出\(n,m\),求有多少种不同构的挺好序列。答案对\(998244353\)取模。

输入格式

一行两个正整数\(n,m\)

输出格式

一行一个整数,表示有多少种不同构的挺好序列。

样例一

input

3 2

output

4

显然\(n<m\)时答案为\(0\)

解决这种题的思路就是找一个“等价条件”来计数。关于区间最大值的问题,我们可以用笛卡尔树。

对于一个笛卡尔树的每个节点\(v\)\(ls_v<v,rs_v\leq v\)。所以,如果一个笛卡尔树的最长左链,也就是根到某个点的路径上经过的左儿子数量(加上根)超过\(m\)的话,那么就是无解的,因为此时至少要分配\(m+1\)个不同的权值。满足条件的话就一定有解。

有一个非常厉害的\(O(nlogn)\)的生成函数做法可以参考这篇博客。

其实还有个\(O(n)\)的做法。

一棵多叉树唯一对应一棵二叉树,反过来也是唯一对应的。一棵\(n\)个二叉树对应一棵\(n+1\)的点的多叉树(要补一个根)。原来的二叉树最长左链\(\leq m\),对应多叉树的最大深度\(\leq m\)(根深度为\(0\))。

考虑用括号序来解决这个问题。一棵\(n+1\)个点的树的可以表示为\((X)\),其中\(X\)表示一个括号序列,左右两个括号是根,所以我们先将其删除,于是对应了一个\(n\)对括号的括号序列。我们设\((\)\(+1\)\()\)\(-1\),那么树的最大深度就是最大的前缀和,所以任意位置的前缀和\(x\)要满足\(0\leq x\leq m\)

我们将这个东西抽象到一个坐标系上。初始起点在\((0,0)\),每次操作使横坐标\(+1\),纵坐标可以\(+1\)或者\(-1\)。要求任意时刻这个点不能达到\(y=m+1\)\(y=-1\)这两条直线,求最终走到\((2n,0)\)的方案数。

折线定理

如果没有任何限制,那么从\((0,0)\)走到\((n,m)\)的方案数是\(C_{n}^{\frac{n+m}{2}}\),设这个东西为\(path(n,m)\)

如果只有一个限制,比如\(y=-1\),那么我们可以用折线定理。将\((0,0)\)沿\(y=-1\)对称到\((0,-2)\),每一条\((0,-2)\to (n,m)\)的路径都唯一对应了原问题的一个非法路径。考虑将第一次达到\(y=-1\)的路径沿着\(y=-1\)对折回来就可以理解了。所以答案为\(path(n,m)-path(n,m+2)\)

如果有两条线,那么就要容斥了。设这条线分别为\(a,b\)。一个非法序列可以表示为类似于\(aaabbb...aaabbb\)的一个序列,表示经过这两条线的情况。于是我们枚举这个序列的一个子序列\(ababab...\)。然后算出一定包含这个子序列的非法序列数。以包含\(ab\)的序列为例,我们先将起点\(p\)沿\(a\)翻折得到\(p'\),然后再将\(p'\)沿\(b\)翻折得到\(p''\),然后算出\(p''\)\((2n,0)\)的方案数\(path(2n,0-{p''}_y)\)。计算跟复杂的序列就多次翻折。容斥系数为\((-1)^k\)\(k\)\(a,b\)的个数和。我们可以发现,\(p\)经过一次翻折,\(p_y\)就会增大,当\(p_y>n\)时,\(path\)一定为\(0\)

代码:

#include
#define ll long long#define N 20000005using namespace std;inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}const ll mod=998244353;int fac[N],ifac[N];ll ksm(ll t,ll x) { ll ans=1; for(;x;x>>=1,t=t*t%mod) if(x&1) ans=ans*t%mod; return ans;}int n,m;void pre(int n) { fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; ifac[n]=ksm(fac[n],mod-2); for(int i=n-1;i>=0;i--) ifac[i]=1ll*ifac[i+1]*(i+1)%mod;}ll C(int n,int m) {return n

转载于:https://www.cnblogs.com/hchhch233/p/10966468.html

你可能感兴趣的文章
真实分享记录我学习Linux系统遇到的问题
查看>>
Linux下查找占用内存最多的进程
查看>>
mongodb 配置文件
查看>>
查看 docker 容器使用的资源
查看>>
Jedis的配置和优化
查看>>
layui + 阿里巴巴iconfont图标库导入
查看>>
2017总结一
查看>>
Spring boot 入门--1
查看>>
MySQL中TIMESTAMPDIFF和TIMESTAMPADD函数的用法
查看>>
Power Designer数据库建模工具,正向、逆向工程
查看>>
Libevent学习-02:搭建CentOS下的开发环境
查看>>
java操作Excel、word和pdf
查看>>
阿里巴巴常考面试题及汇总答案
查看>>
yum install 与 yum groupinstall 的区别
查看>>
Docker Swarm 编排及部署 PostGIS,并操作 GIS 数据
查看>>
当区块链遇上人工智能,这次变革的意义到底有多重大?
查看>>
Linux下安装python
查看>>
Go基础系列:读取标准输入(一)
查看>>
CAD打印文字不显示怎么办
查看>>
js正则表达式全文关键字搜索并高亮
查看>>