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;
}