noip模拟测试(雅礼)T1 卡特兰数

9 篇文章 0 订阅
2 篇文章 0 订阅

题目

今天,接触信息学不久的小 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 n300 m ≤ n ( n − 1 ) 2 m\le \frac{n(n-1)}{2} m2n(n1) f i 、 g i ≤ n f_i、g_i \le n figin

题解

我们令 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值