题目
今天,接触信息学不久的小 A A A刚刚学习了卡特兰数。
卡特兰数的一个经典定义是,将 n n n个数依次入栈,合法的出栈序列个数。
小 A A A觉得这样的情况太平凡了。于是,他给出了 m m m组限制,每个限制形如 ( f i , g i ) (f_i,g_i) (fi,gi),表示 f i f_i fi不能在 g i g_i gi之后出栈。
他想求出:在满足了这 m m m组限制的前提下,共有多少个合法的出栈序列。他不喜欢大数,你只需要求出答案在模 998244353 998244353 998244353意义下的值即可。
数据范围
编号 | 分值 | n n n | m m m | 特殊性质 |
---|---|---|---|---|
1 1 1 | 15 15 15 | ≤ 300 \le 300 ≤300 | = 0 = 0 =0 | − - − |
2 2 2 | 15 15 15 | ≤ 7 \le 7 ≤7 | ≤ 10 \le 10 ≤10 | − - − |
3 3 3 | 15 15 15 | ≤ 100 \le 100 ≤100 | ≤ 50 \le 50 ≤50 | − - − |
4 4 4 | 15 15 15 | ≤ 300 \le 300 ≤300 | − - − | 保证所有的 f i f_i fi相同 |
5 5 5 | $ 20$ | ≤ 300 \le 300 ≤300 | ≤ 300 \le 300 ≤300 | − - − |
6 6 6 | 20 20 20 | ≤ 300 \le 300 ≤300 | − - − | − - − |
对于全部的数据,保证 n ≤ 300 n\le 300 n≤300, m ≤ n ( n − 1 ) 2 m\le \frac{n(n-1)}{2} m≤2n(n−1), f i 、 g i ≤ n f_i、g_i \le n fi、gi≤n
题解
我们令
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示从
i
i
i 到
j
j
j 的出栈顺序的方案数。
考虑到进站出栈的操作实际上形成一棵树,所以我们只考虑后面操作的顺序(无后效性)。所以我们就可以卡特兰数的方法去转移:
枚举一个
k
,
k
∈
[
i
,
j
]
k,k \in [i,j]
k,k∈[i,j] 表示
k
k
k 是最后出栈的点的方案数,然后只在
[
i
,
j
]
[i,j]
[i,j] 中,显然比
k
k
k 小的点都比比
k
k
k 大的点早出栈(这个自己理解一下)。于是就可以进行转移:
f
[
i
]
[
j
]
=
f
[
i
]
[
j
]
+
f
[
i
]
[
k
]
∗
f
[
k
+
1
]
[
j
]
f[i][j]=f[i][j]+f[i][k]*f[k+1][j]
f[i][j]=f[i][j]+f[i][k]∗f[k+1][j]
然后我们考虑限制条件:
显然对于一个限制条件
f
,
g
f,g
f,g ,在枚举
k
k
k 时若
(
i
,
j
,
k
)
(i,j,k)
(i,j,k) 不合法仅当
f
=
k
f=k
f=k 或
f
>
k
f>k
f>k &&
g
<
k
g<k
g<k ,所以这时的效率是
O
(
n
3
m
)
O(n^3m)
O(n3m) 的。
然后我们考虑优化:
想到一个限制条件
(
f
,
g
)
(f,g)
(f,g) 会限制的范围其实是固定的,所以我们能发现上述的限制条件在二维平面在形成了若干个区间,我们从中用前缀和判断合不合法即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int k=0,f=1;char ch;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')k=k*10+ch-'0',ch=getchar();
return k*f;
}
const int N=305;
const ll P=998244353;
int n,m;
ll ans,f[N][N],s[N][N];
int pd(int x,int X,int y,int Y){
if(x>X||y>Y)return 0;
return s[X][Y]-s[x-1][Y]-s[X][y-1]+s[x-1][y-1];
}
int main(){
n=read();m=read();
for(int i=1;i<=m;++i){
int x=read(),y=read();
s[y][x]++;
}
for(int i=1;i<=n;++i)f[i][i]=f[i+1][i]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
for(int len=1;len<=n;++len){
for(int i=1;i+len<=n;++i){
int j=i+len;
for(int k=i;k<=j;++k){
if(!pd(i+1,k,k+1,j)&&(!pd(i+1,k,i,i))&&(!pd(i,i,k+1,j)))f[i][j]=(f[i][j]+f[i+1][k]*f[k+1][j]%P)%P;
}
}
}
printf("%lld\n",f[1][n]);
return 0;
}