Codeforces Global Round 22 E. Balance Addicts

题目链接

题意:

给了一个长为n的数组,将数组里面的数分为任意个连续的子区间,问有多少种情况使得这些子区间的和组成回文的形式
例如:

在这里插入图片描述
可以有图示三种分法及原区间

题解:

要将原数组分为回文的区间,即首尾区间和对应相等,考虑从头和尾同时往上走,即前k个区间和与后k个区间和肯定也相等,自然联想到前缀和与后缀和,只要满足从前缀和里面选出的一些数与从后缀和里面选出的一些数,首尾对应相等,则一定可以对应一种区间分法使得区间和对应相等(所有相邻的前缀相减相邻的后缀相减还原出对应的区间)
例如
在这里插入图片描述
可以选第一个前缀与最后一个后缀,第二个前缀与倒数第二个后缀(前缀一定选在后缀前面不能有相交)

继续考虑怎么选出一些前缀后缀对应相等
因为数组元素大于等于0,可以发现前缀与后缀都是递增的,即某一段连续相等的前缀最多只有一段连续相等的后缀与其对应相等,且所有前后缀相等的区间一定是互相独立的,即可以把所有前缀后缀对应相等的区间单独拿出来计算方案数后相乘,双指针处理一下即可

注意 :中间一段可能重合,即该部分前缀对应该部分后缀,特殊处理一下


代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353; 
const ll N=2e5+5;
ll jc[100005];
ll jcm[100005];
ll ksm(ll p,ll q){
	ll sum=1;
	while(q!=0){
		p=p%mod;
		if(q%2==1){
			sum=sum*p%mod;
			p*=p;q/=2;
		}
		else{
			p*=p;q/=2;
		}
	}
	return sum;
}
ll c(ll n,ll m){
	if(n==0&&m==0)return 1;
	return jc[n]*jcm[m]%mod*jcm[n-m]%mod;
}
struct duqi{
	ll n1,n2;
};
duqi q[N];
ll a[N]; 
ll sumq[N],sumh[N];
int main()
{
	ll i,j,kt,jw,t,p,m,p1,r,mid,max1,l,g,n,k,min1,w1,v,l1,r1,x,y,p2,h,k1,i1,j1,sum,n1,n2;
	//printf("%lld",c(0,0));
	jc[0]=1;jcm[0]=1;
	for(i=1;i<=100000;i++){
	jc[i]=jc[i-1]*i%mod; }
	jcm[100000]=ksm(jc[100000],mod-2);
	for(i=99999;i>=1;i--)jcm[i]=(jcm[i+1]*(i+1))%mod;
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&n);
		v=0;p=0;
		for(i=1;i<=n;i++)scanf("%lld",&a[i]);
		sumq[0]=0;sumh[n+1]=0;
		for(i=1;i<=n;i++)sumq[i]=sumq[i-1]+a[i];
		for(i=n;i>=1;i--)sumh[i]=sumh[i+1]+a[i];
		for(i=1,j=n;i<j;){
			if(sumq[i]!=sumh[j]){
				if(sumq[i]>sumh[j])j--;
				else
				i++;
			}
			else{
				n1=0;n2=0;g=sumq[i];
				while(sumq[i]==g&&i<=n){
					i++;n1++;
				}
				while(sumh[j]==g&&j>=1){
					j--;n2++;
				}
				//printf("%lld ",n1);
				if(i>j+1)v=n1+1;
				else{
					q[++p].n1=n1;q[p].n2=n2;
				}
			}
		}
		for(i=1;i<=n;i++)if(a[i]!=0)break;
		if(i>n){
			printf("%lld\n",(ksm(2,n-1))%mod);continue;
		}
		k=1;
		//printf("%lld ",v);
		for(i=1;i<=p;i++){
		sum=0;
		for(j=0;j<=min(q[i].n1,q[i].n2);j++){
			sum=(sum+c(q[i].n1,j)*c(q[i].n2,j)%mod)%mod;
		}k=k*sum%mod;
	}
	sum=0;
		for(i=0;i<=v;i+=2)sum=(sum+c(v,i))%mod;
		printf("%lld\n",k*sum%mod);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值