CF1738 E. Balance Addicts

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;
}
posted @ 2022-10-01 14:56  FxorG  阅读(56)  评论(0编辑  收藏  举报