Description
传送门
- 求有多少个n排列可以被一下的栈排序排成升序,其中有一个元素已经确定
- 每一次可以对序列进行入栈和出栈两种操作。
- 入栈操作:如果序列非空,那么三叶会取出序列的第一个元素,把它扔进栈的顶部。
出栈操作:如果栈非空,那么三叶可以取出栈的顶部,把这个元素接在结果序列的末尾。
- 注意结果序列和原序列是不一样的,也就是出栈操作不是把元素接在原序列后。
如果所有元素都经过了一次入栈和出栈操作,得到的结果序列为升序,那么这个序列就被排好序了。
- n<=1e6
Solution
- 题解很迷,我很菜,不会怎么推,题解的方法运用看似思路清晰的DP,找出性质,即不存在大小关系为(2,3,1)的三元组,设f[i][j]表示从后往前倒i,当前加入的最小值为j。然后经过一系列的优化,这里就不再赘述。
- 然而实际上有一种更加巧妙并且简明的做法(%%%lyl)。
- 看到栈,我们可以考虑将序列转化为一个括号序。
- 其中左括号表示在栈中加入一个元素,右括号表示弹栈。
- 根据最后是升序的,我们可以确定,第i个右括号代表的就是数字i。
- 那么与其匹配的左括号就是它加入的顺序了。
- 如果pos位置固定为x,那么x的左括号就是第pos个,枚举它出现在2*n的括号序中的第i个,可以把x对应的右括号所在的位置推出来。
- 剩下的贡献就是一个p个左括号,q个右括号,序列合法的个数。
有关括号排列
- n对括号排列数是卡特兰数n+1C2nn
- 入栈出栈的排列也是这个。
- 那x个左括号,y个右括号呢?
- 考虑n对括号相当于从(0,0)走到(n,n),不经过(i,i+1),将因为可以将向右看做一个左括号,向左看做一个右括号。
- x个左括号,y个右括号也是一样的。
- 而不经过直线y=x+1的方案可以考虑,将(x,y)终点作y=x+1对称点,那么(0,0)到(x’,y’)的方案一定唯一对应一种不合法的方案。即从第一个接触y=x+1直线的地方开始翻折过去就唯一对应了。
- 这样就是两个组合数相减了。
有关组合数求和
- ∑i=0mC(n+i,i)=C(n+1,m+1).即一条斜线上的组合数是右下角一个位置的组合数。
- 因为将杨辉三角形画出来:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
- 将每一斜排横过来:
1 1 1 1 1 .
1 2 3 4 5 …
1 3 6 10 …
1 4 10 …
1 5 …
1…
- 这其实是对上一行(也就是上一斜排)求一次前缀和,根据C(n,m)=C(n-1,m)+C(n-1,m-1)推来也是一样的。
- 那么自然一排就是右下的那个了。
- 同理也有∑i=mnC(i,m)=C(n+1,m+1)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 2000005
#define ll long long
#define mo 1000000007
using namespace std;
int n,pos,x,i,j,k;
ll fct[maxn],ift[maxn],ans;
ll ksm(ll x,ll y){
ll s=1;
for(;y;y/=2,x=x*x%mo) if (y&1)
s=s*x%mo;
return s;
}
ll C(int n,int m){
if (m<0||n<m) return 0;
return fct[n]*ift[m]%mo*ift[n-m]%mo;
}
ll F(int i,int j){
if (i<0||j<0) return 0;
if (i<j) return 0;
return C(i+j,i)-C(i+j,j-1);
}
int pd(int x){return 0<=x&&x<=2*n;}
int main(){
scanf("%d%d%d",&n,&pos,&x);
fct[0]=1;for(i=1;i<maxn;i++) fct[i]=fct[i-1]*i%mo;
ift[maxn-1]=ksm(fct[maxn-1],mo-2);
for(i=maxn-2;i>=0;i--) ift[i]=ift[i+1]*(i+1)%mo;
for(i=1;i<=2*n;i++) {
int x0=pos-1,y0=i-pos;
int x1=x-1-y0,y1=x-1-y0;
j=i+x1+y1+1,k=2*n-j;
int x2=n-x0-x1-1,y2=n-y0-y1-1;
ans+=F(x0,y0)*F(x1,y1)%mo*F(y2,x2)%mo;
}
printf("%lld",(ans%mo+mo)%mo);
}