【简单计数知识】JZOJ6395. 【NOIP2019模拟2019.10.28】消失的序列

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对括号排列数是卡特兰数C2nnn+1frac{C_{2n}^{n}}{n+1}
  • 入栈出栈的排列也是这个。
  • 那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)sum_{i=0}^{m}C(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)sum_{i=m}^{n}C(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);
}

原文地址:https://www.cnblogs.com/DeepThinking/p/13090932.html