0%

[CF1738E] Balance Addicts

一道有趣的数列题,然而场上没有做出来。

看完题解发现跟我想的差不多,就是我码风太屎

里面分类讨论和组合数学的部分感觉出的很不错。

主要是对官方题解的翻译,并加入了我的理解。

题目链接

题意

现在有一个数列 你需要将这个数列分为若干非空的段。

假设你将这个数列分成了 段,分成的所有段中,第 段中所有数字的和为

我们称一种分的方法为平衡的,当且仅当对于每一个 ,都有

求一共有多少种平衡的分法,对 取模。

多组数据。

题解

首先我们来考虑一个暴力的思路。

为区间 的答案,我们要求的就是

我们暴力枚举最左边和最右边区间的范围,进行转移。

时间复杂度是 的。

虽然这个时间复杂度无法接受,但是这个方法可以优化。

现在我们来看具体求一个区间 的方法,大力分类讨论:

  1. ,那么显然所有方法都可行,所以答案为

  2. ,我们设区间 最左边共有 个连续的 ,最右边一共有 个连续的

    如果分成的区间中,最左边和最右边 段的和都为 ,那么此时方案数为

    其中 表示将左边的 分成 段(左边的 段是上一行说的和为 段,最后一段给区间 最左边的一段分过去),并且其中 段要求非空的方案数, 同理。

    最后整个乘以 表示考虑了最左边和最右边的 段以后,中间切分的方案数。

    所以此时最终答案为

  3. 对于剩下的情况,我们设:

    最小的 使得存在一个 满足:

    一个最大的 使得对于上述的 满足

    这个 可以使用双指针去找。

    然后再进行分类讨论:

    1. 如果 ,说明这个序列不存在分成 段的方案,所以此时答案为

      并且可以注意到,对于所有剩余的情况,肯定会有 ,并且

    2. 可以知道,此时当且仅当 中所有元素在同一段里, 中所有元素在同一段里,这个切分方案就是合法的。

      所以此时答案为

    3. 对于剩下的所有情况,我们设区间 中最左边共有 个连续的 ,最右边共有 个连续的

      当且仅当满足这三个条件是,一个切分方案是合法的:

      1. 区间 在同一段, 在同一段。
      2. 区间 中和为 的段数=区间 中和为 的段数。
      3. 区间 的切分方案合法。

      所以我们还是枚举区间 中共有 段和为

      这时方案数为

      其中 表示将左边这些 分为 段(中间的 段是上面说的和为 段,最左边的一段给 ,最右边的一段给 ),并且中间 段非空的方案数。 同理。

      所以此时的方案数为

      之所以有一个 ,是因为存在方案,使得 全部属于 的最左边的一段, 全部属于 的最右边的一段。

直接递归就可以了,细节不算多,但是也不算特别少。

时间复杂度

需要预处理阶乘及其逆元,也可以做到

代码

我懒了亿点,预处理写的是 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
ll qow(ll a,int b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll inv[200001];
ll jc[200001];
int pre[100001];
int a[100001];
int n;
inline void init()
{
jc[0]=inv[0]=1;
for(int i=1;i<=200000;i++)
{
jc[i]=jc[i-1]*i%mod;
inv[i]=qow(jc[i],mod-2);
}
}
inline ll C(int a,int b)
{
return jc[a]*inv[b]%mod*inv[a-b]%mod;
}
inline ll solve(int l,int r)
{
if(pre[r]-pre[l-1]==r-l+1) return qow(2,r-l);
if(a[l]==0&&a[r]==0)
{
int x=0,y=0;
while(a[l]==0) x++,l++;
while(a[r]==0) y++,r--;
ll ans=0;
for(int i=0;i<=x&&i<=y;i++) ans=(ans+C(x,i)*C(y,i))%mod;
ans=ans*solve(l,r)%mod;
return ans;
}
ll suml=0,sumr=0;int x=l-1,y=r+1;
while(suml!=sumr||suml==0)
{
if(suml<=sumr) suml+=a[++x];
else sumr+=a[--y];
}
if(y==l||x==r) return 1;
if(pre[y-1]-pre[x]==y-x-1) return qow(2,y-x);
int l0=0,r0=0,nx=x+1,ny=y-1;
while(a[nx]==0) l0++,nx++;
while(a[ny]==0) r0++,ny--;
ll ans=1;
for(int i=0;i<=l0&&i<=r0;i++) ans=(ans+C(l0+1,i+1)*C(r0+1,i+1))%mod;
return ans*solve(nx,ny)%mod;
}
int main()
{
init();
int T=read();
while(T--)
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
pre[i]=pre[i-1]+(a[i]==0);
}
printf("%lld\n",solve(1,n));
}
return 0;
}

感谢观看!