题解 CF1770G Koxia and Bracket
更差的阅读体验(bushi)
其实 2022 年省选前联考出过类似的套路,但当时太鸽了就没有总结。
阶梯网格计数问题是指以下一类问题:
问题:给定一个
n 列阶梯状网格图,第i 列高度为c_i (保证c_i 不降),每次可以向上或向右走一步,不能超出网格边界(即所有经过的点(x,y) 必须满足0\le x\le n,0\le y\le c_x ,问从左下到右上有多少种方案。对998244353 取模。n\le 10^5
首先
考虑最特殊的情况:如果没有
那么究竟应该怎么处理呢?我们先做一个转化:在原网格中,考虑左上到右下的那条对角线,我们分别计算出左下角和右上角到对角线上每个点的方案数,然后枚举中间点计算方案数累加即可。显然左下和右上两部分的计算是对称的,因此只用考虑一边,不妨考虑左下角。
假设有如下图所示的网格图,其中阴影部分表示对角线以下的部分:
那么我们考虑将第
换句话说,在上面那幅图中,从
于是问题可以转化为:有一张网格图,第
那么问题就来了,这样变换有什么好处呢?还是延续上文中的分治 NTT 做法,假设我们要计算
这样原问题就出现了突破口,考虑具体怎么实现,方便起见,设
容易发现传入的多项式、返回的多项式、进行卷积的多项式的长度都与区间长度同阶。因此总复杂度
回到 CF 这个题上来。将左括号视作
const int MAXN=5e5;
const int MAXP=1<<20;
const int MOD=998244353;
const int pr=3;
const int ipr=332748118;
int fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++)
ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++){
fac[i]=1ll*fac[i-1]*i%MOD;
ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
}
int binom(int n,int k){return 1ll*fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;}
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD)if(e&1)ret=1ll*ret*x%MOD;
return ret;
}
int rev[MAXP+5];
void NTT(vector<int>&a,int len,int type){
int lg=31-__builtin_clz(len);
for(int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<lg-1);
for(int i=0;i<len;i++)if(rev[i]<i)swap(a[i],a[rev[i]]);
for(int i=2;i<=len;i<<=1){
int W=qpow((type<0)?ipr:pr,(MOD-1)/i);
for(int j=0;j<len;j+=i){
for(int k=0,w=1;k<(i>>1);k++,w=1ll*w*W%MOD){
int X=a[j+k],Y=1ll*w*a[(i>>1)+j+k]%MOD;
a[j+k]=(X+Y)%MOD;a[(i>>1)+j+k]=(X-Y+MOD)%MOD;
}
}
}
if(type<0){
int iv=qpow(len,MOD-2);
for(int i=0;i<len;i++)a[i]=1ll*a[i]*iv%MOD;
}
}
vector<int>conv(vector<int>a,vector<int>b){
int LEN=1;while(LEN<a.size()+b.size())LEN<<=1;
a.resize(LEN,0);b.resize(LEN,0);NTT(a,LEN,1);NTT(b,LEN,1);
for(int i=0;i<LEN;i++)a[i]=1ll*a[i]*b[i]%MOD;
NTT(a,LEN,-1);return a;
}
char s[MAXN+5];int n,sum[MAXN+5];pii mn=mp(0,0);
int lim[MAXN+5],len,res;
vector<int>solve(int l,int r,vector<int>F){
int pos=max(lim[r]-(r-l),0);
if(r-l==1){
vector<int>res(lim[r]-pos+1);
for(int i=0;i<F.size();i++)for(int j=i;j<res.size();j++)
res[j]=(res[j]+F[i])%MOD;
return res;
}
int mid=l+r>>1,pos_mid=max(lim[r]-(r-mid),0),pos_l=max(lim[mid]-(mid-l),0);
vector<int>nF,G(lim[mid]-pos+1),H(lim[r]-pos+1),G1,G2,H1,H2,A,B;
for(int i=pos_l;i<=lim[l];i++)nF.pb(F[i-pos]);
G1=solve(l,mid,nF);
for(int i=pos_l;i<=lim[mid];i++)G[i-pos]=(G[i-pos]+G1[i-pos_l])%MOD;
if(pos_l!=pos){
A.clear();B.clear();
for(int i=pos;i<pos_l;i++)A.pb(F[i-pos]);
for(int i=0;i<=mid-l;i++)B.pb(binom(mid-l,i));
G2=conv(A,B);
for(int i=0;i<min(G.size(),G2.size());i++)G[i]=(G[i]+G2[i])%MOD;
}
nF.clear();
for(int i=pos_mid;i<=lim[mid];i++)nF.pb(G[i-pos]);
H1=solve(mid,r,nF);
for(int i=pos_mid;i<=lim[r];i++)H[i-pos]=(H[i-pos]+H1[i-pos_mid])%MOD;
if(pos!=pos_mid){
A.clear();B.clear();
for(int i=pos;i<pos_mid;i++)A.pb(G[i-pos]);
for(int i=0;i<=r-mid;i++)B.pb(binom(r-mid,i));
H2=conv(A,B);
for(int i=0;i<min(H.size(),H2.size());i++)H[i]=(H[i]+H2[i])%MOD;
}
// printf("solve %d %d %d <",l,r,pos_mid);
// for(int i=0;i<F.size();i++)printf("%d%c",F[i],",>"[i+1==F.size()]);
// printf("\n");
// for(int x:G)printf("%d ",x);printf("\n");
// for(int x:H)printf("%d ",x);printf("\n");
return H;
}
int calc(){
if(!len)return 1;
for(int i=1;i<=len;i++)lim[i]=i-lim[i];
// for(int i=1;i<=len;i++)printf("%d%c",lim[i]," \n"[i==len]);
vector<int>vec=solve(0,len,vector<int>{1});
return vec.back();
}
int main(){
scanf("%s",s+1);n=strlen(s+1);init_fac(MAXN);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+((s[i]=='(')?1:-1);
for(int i=1;i<=n;i++)chkmin(mn,mp(sum[i],i));
memset(lim,0,sizeof(lim));int cur=0,cmn=0;
for(int i=1;i<=mn.se;i++){
if(s[i]=='(')cur++;
else{
cur--;chkmin(cmn,cur);
lim[++len]=-cmn;
}
}
res=calc();
memset(lim,0,sizeof(lim));len=cur=cmn=0;
for(int i=n;i>mn.se;i--){
if(s[i]==')')cur++;
else{
cur--;chkmin(cmn,cur);
lim[++len]=-cmn;
}
}
res=1ll*res*calc()%MOD;
printf("%d\n",res);
return 0;
}
/*
())())
())())))((())))))
*/