https://codeforc.es/contest/1738/problem/E
考虑回文的构造最典的一定是 2 边向中间不断扩展的形式,这启发我们从这个方面着手思考。
考虑 \(f(l,r)\) 为区间 \([l,r]\) 的答案,则显然我们要选择若干组相等的前后缀和进行递归处理。
考虑 \(a\ge 0\),所以前后缀和单不降,所以倘若 \((x,y)\),满足 \(a[l,x]=a[y,r]\),则显然不会出现,\((X,Y),X<x,Y<y\) 的情况出现,也就是说,倘若我们把当前合法二元组看成线段的话,那么显然所有线段是互相包含的。于是,我们可以找到唯一没有被包含的线段,则无论如何,一组合法的方案一定包含这个前后缀相等,因此可以将其赋值为 \(0\),计算下去即可。
考虑可能有前后缀 \(0\) 出现,那么这个样子线段就会出现可能不包含的情况。
于是,我们考虑能否通过计算贡献,然后计算消除前后缀 \(0\) 的答案的形式,来消除前后缀 \(0\) 的影响。
显然,可以考虑将前后缀 \(0\) 划分成若干段即可。
为啥正确,显然倘若划分不完,多出来的 \(0\) 对和无影响,自然归入下一部分的计算答案中了。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=(int)(1e5+5),mod=998244353;
int n,a[N],cnt[N],jie[N],djie[N];
int fpow(int x,int y) {
int res=1; x%=mod;
while(y) {
if(y&1) res=res*x%mod;
y>>=1; x=x*x%mod;
}
return res;
}
int C(int n,int m) {
if(m>n||n<0||m<0) return 0;
return jie[n]*djie[m]%mod*djie[n-m]%mod;
}
int solve(int l,int r) {
if(l>=r) return 1;
// for(int i=l;i<=r;i++) {
// if(a[i])
// }
int L=l-1,R=r+1;
while(L+1<=r&&a[L+1]==0) ++L;
while(R-1>=l&&a[R-1]==0) --R;
if(L>=R) {
return fpow(2,r-l);
}
if(L>=l&&R<=r) {
int res=0,num1=L-l+1,num2=r-R+1;
for(int i=0;i<=min(num1,num2);i++) res=(res+C(num1,i)*C(num2,i)%mod)%mod;
return res*solve(L+1,R-1)%mod;
} else {
L=l-1; R=r+1;
int resa=0,resb=0;
do {
if(resa>=resb) --R,resb+=a[R];
else ++L,resa+=a[L];
if(L>=R) return 1;
} while(resa!=resb||L<l||R>r);
// cout<<L<<" : "<<R<<'\n';
if(L>=R) return 1;
a[L]=a[R]=0; //下面的无论如何一定选这 2 段
return solve(L,R);
}
}
void sol() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int ans=solve(1,n); ans=(ans%mod+mod)%mod;
cout<<ans<<'\n';
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
jie[0]=djie[0]=1; for(int i=1;i<=N-5;i++) jie[i]=jie[i-1]*i%mod,djie[i]=fpow(jie[i],mod-2);
int T; cin>>T; while(T--) sol();
return 0;
}