先判掉 \(k>\min(n,m)\) 的情况。
首先有个明显的计算最长合法括号子序列的贪心方法:初始一个值为 \(0\) 的计数器,从左到右枚举每个字符,遇到 (
时将计数器加一,遇到 )
时如果计数器不为 \(0\) 就减一然后将答案加一。
考虑绘制它的折线图。初始时纵坐标为 \(0\),从左到右枚举每个字符,遇到 (
加一,遇到 )
减一。考虑有多少个 )
不会被匹配到,即在开头需要加入的最少 (
的数量使得折线不会有在横轴下方的部分。于是题目中的限制就转化为了要使折线能到达的最低点纵坐标为 \(k-m\)。
记 \(f(k)\) 表示能到达的最低点纵坐标为 \(k-m\) 的折线个数,那么答案就是 \(f(k)-f(k-1)\)。考虑将折线最后一次碰到直线 \(y=k-m\) 以后的部分上下翻转,那么就转化为了从 \((0,0)\) 走到 \((n+m,2k-m-n)\) 的路径方案数,组合数直接求即可。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define mxn 4003
#define md 1000000007
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int t,n,m,k;
ll fac[mxn],ifac[mxn];
ll power(ll x,int y){
ll ans=1;
for(;y;y>>=1){
if(y&1)ans=ans*x%md;
x=x*x%md;
}
return ans;
}
inline ll C(int n,int m){
if(n<m||m<0)return 0;
return fac[n]*ifac[m]%md*ifac[n-m]%md;
}
int f(int k){
return C(n+m,(n+m-(2*k-m-n))>>1);
}
void init(int n){
fac[0]=1;
rep(i,1,n)fac[i]=fac[i-1]*i%md;
ifac[n]=power(fac[n],md-2);
drep(i,n,1)ifac[i-1]=ifac[i]*i%md;
}
signed main(){
init(4e3);
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&k);
if(k>min(n,m)){
puts("0");
continue;
}
cout<<(f(k)-f(k-1)+md)%md<<'\n';
}
return 0;
}
标签:md,CF1924D,ifac,int,题解,ll,ans,Balanced,fac
From: https://www.cnblogs.com/zifanoi/p/18115318