@ysner
2018-07-24T21:08:50.000000Z
字数 2285
阅读 2210
DP
我们可以对任一序列构造笛卡尔树。笛卡尔树是一棵二叉树,它符合小根堆的性质,并且它的中序遍历就是该序列。如果序列中的数两两不同,那么笛卡尔树是唯一的。以下是一个笛卡尔树的例子:
图图
(概括一下,就是每次在当前区间中选出值最小结点作为根,然后把根两边区间分别的最小值作为根的两个儿子,然后再分别取根两边区间向下递归)
如此定义一棵笛卡尔树的值:
询问如果以所有的排列构建笛卡尔树,那么其中值不超过的个数。
认真表示此题和分组一题极为相似,我考场上竟然没切掉。。。
或许是因为我没有认真考虑加排列的过程,而是认为排列是既成的。
我是不会承认我看了一小时图才发现正确建树方式的
暴枚排列+模拟建树,复杂度。
只有种取值,还这么小?
这是不是叫我们打表找规律。。。
时,;(
时,()
时,()
时,并不知道是什么规律。
于是回答就可以了。
显然,建树是有一个过程的。
我们每给当前的排列从小到大加一个数,就有几种决策:
依照决策,我们似乎能猜猜状态:表示当前排列有个数,值为,有个结点可匹配的方案数。
但是,这么就有一个问题,你并不知道新结点是与哪个结点匹配的,因而无法求得值。
我们可以用一种很套路的差分方法,就是新点接在叶子结点上时减去新点权值,等它被匹配时再加上匹配点的权值。
但是并不一定所有点都会被匹配啊!
所以,我们还要多一维状态,维表示当前有个结点最后被匹配完毕的方案数(而个结点并不一定被匹配)。
第一个决策也要分为两类:新结点将匹配和新结点将不匹配。于是个决策。
第二个问题是按差分方法很有可能减着减着就成负数了。
想一想,每当我们加一个数,就相当于让所有的还未配对完毕的都加。
因为它们要更晚才能被配对。
因此,每次转移时,即可。
好像新结点接在未匹配结点上的方案数也值得思考。
注意到,个结点能接个,而每加一个结点,占用了个能接的位置,又能接个。因此,每加一个结点,能接的位置+。
即个结点能接个。
而又有个位置是不能占的,因此方案数为。
时间复杂度上限,空间复杂度滚动后。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
int n,m,mod,f[2][155][155][155],now,nxt=1;
ll ans;
bool vis[200];
il ll gi()
{
re ll x=0,t=1;
re char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
int main()
{
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
n=gi();m=gi();mod=gi();
f[1][0][0][0]=1;
fp(i,1,n-1)
{
fp(j,0,m)
fp(k,0,i)
fp(l,k,i)
{
re int tmp=f[nxt][j][k][l],t=j+k;f[nxt][j][k][l]=0;
if(!tmp||t>m) continue;
if(k) (f[now][t][k-1][l-1]+=1ll*tmp*k%mod)%=mod;
if(i+1-l>0) (f[now][t][k][l+1]+=1ll*tmp*(i+1-l)%mod)%=mod;
if(i+1-l>0) (f[now][t][k+1][l+1]+=1ll*tmp*(i+1-l)%mod)%=mod;
}
swap(now,nxt);
}
fp(j,0,m)
fp(l,0,n)
(ans+=f[nxt][j][0][l])%=mod;
printf("%lld\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}