题意:求n个1,m个-1组成的所有序列中,最大前缀之和。
首先引出这样一个问题:使用n个左括号和m个右括号,组成的合法的括号匹配(每个右括号都有对应的左括号和它匹配)的数目是多少?
1.当n=m时,显然答案为卡特兰数$C_{2n}^{n}-C_{2n}^{n+1}$
2.当n<m时,无论如何都不合法,答案为0
3.当n>m时,答案为$C_{n+m}^{n}-C_{n+m}^{n+1}$,这是一个推论,证明过程有点抽象,方法是把不合法的方案数等价于从(0,-2)移动到(n+m,n-m)的方案数,详见https://blog.csdn.net/x_1023/article/details/78290683
回到题目,如果把1看成右括号,把-1看成左括号,那么最大前缀和为0相当于匹配合法,就是上面讨论的第三种情况。
如果进一步扩展,最大前缀和为1,2,3,...,k的情况该如何处理呢?
考虑最大前缀和大于等于k的情况,其实根据上面的方法,可以等价于从点(0,-2k)走到点(n+m,n-m)的方案数,即$C_{n+m}^{n+k}$,前提是$max(m-n,0)leqslant kleqslant m$,然后差分一下就能得到最大前缀和等于k时的方案数了。复杂度$O(n+m)$。由于题目中的n和m分别代表右括号和左括号,所以n和m要反过来。
自己的组合数学真是太辣鸡了,还是要提高一下姿势水平~
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=4000+10,mod=998244853; 5 int n,m,inv[N],f[N],invf[N],ans[N]; 6 int C(int n,int m) {return n<m?0:(ll)f[n]*invf[m]%mod*invf[n-m]%mod;} 7 int main() { 8 inv[1]=f[0]=invf[0]=1; 9 for(int i=2; i<N; ++i)inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod; 10 for(int i=1; i<N; ++i)f[i]=(ll)f[i-1]*i%mod,invf[i]=(ll)invf[i-1]*inv[i]%mod; 11 scanf("%d%d",&n,&m); 12 for(int i=max(n-m,0); i<=n; ++i)ans[i]=C(n+m,m+i); 13 for(int i=max(n-m,0); i<n; ++i)ans[i]=(ans[i]-ans[i+1]+mod)%mod; 14 int sum=0; 15 for(int i=max(n-m,0); i<=n; ++i)sum=(sum+(ll)i*ans[i]%mod)%mod; 16 printf("%d ",sum); 17 return 0; 18 }