0%

[LGR-133] 计数题

一道很神仙的结论题,很考验观察能力

题目链接

题意

给定一个长度为 串,可以进行若干次操作,每一次操作是选择一个长度为 的子串,并将这个串替换为三个数的中位数。

问可以得到多少种不同的串,对 取模。

数据组数

题解

首先转换一下这个操作,其实相当于,要么选择两个相邻的不同数然后删掉(要求删完之后串非空),或者对于三个相邻相同的数,删掉其中两个。

然后下面是题解中需要用到的一些小结论。

引理 :对于一个长度为奇数的子串,存在方案把他删成只剩 位。

证明:显然,若存在两种数字,则可以进行第一种操作,否则可以进行第二种操作,删到只剩一位为止。

引理 :对于一个长度为偶数的区间 ,若 ,则区间 可以被删空

证明:根据引理 ,区间 能被删成只剩一位。

此时若这一位与 不相同,则可以用操作 把这两个一块删掉。

否则剩余的三个数字相同,可以用操作 删掉。

同理,若将条件换为 ,则区间 也可以被删空。

称原串为 ,考虑能否通过操作变成某个串

根据一些做题的套路,我们应该要对 的每一位在 上做贪心匹配。

具体来说,对于一个点 ,他有两条出边 ,出边 连向比 大的最小的满足 可以删空的最小的

假如我们能建出这个自动机,我们统计到的答案一定是能变出的串。

那么如何证明所有能变出的串都会被统计上呢?其实只需要证明每一次转移最小的 是最优的就好了。

假设有三个位置 满足 都能删空,那么只需要证明对于任意一个 ,若 能删空则 能删空即可。

容易发现,若 能够被删空,则上述条件自然成立,而这恰好是引理 的内容。

所以这个自动机一定可以算出所有合法的答案,现在问题在于如何建出这个自动机。


对于一个点 来说,连向的点 下标一定和 奇偶性不同,所以这部分后文默认 奇偶性不同。

假设

对于 边来说, 一定是最小的位置满足 。因为 前面全都是 ,所以区间 一定可以删空。

对于 边来说, 是最小的位置满足 。根据引理 ,区间 可以被删空。

当然我们还需要证明对于 ,区间 删不空。

这是容易的,注意到删到最后一定会有两个 相邻(并且左边的 位置与 同奇偶)。而如果进行操作之前没有 相邻,操作之后也不会有 相邻。

是最小的位置满足 ,所以前面的 一定删不空。

这样我们就能在 复杂度对一个串建出这个自动机。


现在还有一点问题,就是我们连好了中间的边,但是没有确从点 连出的边,以及哪些位置可以被统计入答案。

先来考虑第二个问题。

这相当于我们需要知道,对于每一个和 同奇偶的位置 能不能删空。

首先,若 ,则根据引理 ,区间一定可以被删空。

所以只需要考虑 的情况。

观察可以发现,可以删空等价于存在与 不同奇偶的位置 满足

证明如下:

根据上面的讨论,若条件满足,则区间 能删空,根据引理 可以知道区间 一定也可以删空。

若条件不满足,考虑在 位置补一个虚点,上面的数字是

首先可以知道 不同奇偶,根据上面自动机的讨论,在补完这个位置之后, 删不空,所以不补这个位置的时候, 一定也删不空。

这样就证完了。

开头的问题和结尾基本一样,要注意的是开头两种边只能各连一条。

时间复杂度

代码

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
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
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;
}
inline ll lread()
{
ll 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;
}
const int mod=998244353;
int nx1[5000005][2];
int nx2[5000005][2];
ll f[5000005];
char s[5000005];
int n;
inline void Add(ll &a,ll b)
{
a+=b;
if(a>=mod) a-=mod;
}
inline void solve()
{
scanf("%s",s+1),n=strlen(s+1);
memset(nx1,0,sizeof(nx1));
memset(nx2,0,sizeof(nx2));
memset(f,0,sizeof(f));

nx1[n+1][0]=nx1[n+1][1]=nx2[n+1][0]=nx2[n+1][1]=n+1;
nx1[n+2][0]=nx1[n+2][1]=nx2[n+2][0]=nx2[n+2][1]=n+2;
for(int i=n;i;i--)
{
nx1[i][0]=nx1[i+2][0],nx1[i][1]=nx1[i+2][1];
nx2[i][0]=nx2[i+2][0],nx2[i][1]=nx2[i+2][1];
nx1[i][s[i]-'0']=i;
if(i!=1&&s[i]==s[i-1]) nx2[i][s[i]-'0']=i;
}

f[1]=1;ll ans=0;
for(int i=3;i<=n;i+=2) if(s[i]!=s[1]&&s[i]==s[i-1]){ f[i]=1;break;}
for(int i=1;i<=n;i++)
{
int p=s[i]-'0';
if(nx1[i+1][!p]<=n) Add(f[nx1[i+1][!p]],f[i]);
if(nx2[i+1][p]<=n) Add(f[nx2[i+1][p]],f[i]);
if((n-i)%2==0&&(s[i]==s[n]||nx2[i+1][p]<=n)) Add(ans,f[i]);
}
printf("%lld\n",ans);
}
int main()
{
int T=read();
while(T--) solve();
return 0;
}