CF1383E:Strange Operation题解
CF1383E
题意:输入01串,有如下操作:相邻的 10,01,11 变成 1,或者 00 变成 0,求若干次操作后得到的本质不同的新 01 串数量。
Solution:
两个数变成一个数,可以视作删掉一个数。
如果是可以删除任意数字若干次,那就是子序列自动机上的dp,说白了就是 \(f[i] = f[lst[1]] + f[lst[0]] +1\)。
但显然这题里删除 1 的条件比删除 0 要苛刻,对于一个 1 两侧的 0,想要让它们汇合就必须删掉中间的 1,而删掉中间的 1 则需要删除任意一侧全部的 0。
我们把相邻两个 1 中间的 0 的数量提取出来,形成一个新序列 a(首尾 0 的个数也算进来),相邻两个数都是 1,那么 a 的这一项就是 0。每一个 a 序列对应一个 01 串,输入的 01 串以及我们求的各个本质不同 01 子串都是如此。
考虑可以生成的 01 子串对应的 a 序列有哪些要求(为方便描述,原 01 串的 a 序列用 A 表示)。
a 序列的生成方式就是 A 删除一些位置,并将保留的数字任意减小。删除一个数的含义是指不仅删除两个 1 之间所有的 0,还把 1 给删掉(在某一侧 0被删完时,1 也可以被删除,前提是删 0 的那一侧不是首部或末尾)。首部和末尾的 0,可以删除任意个,删除之后对原串没有任何影响,最终答案乘上两端 0 的数量单独处理。
对于中间,考虑本质不同的 a 序列有多少种。如果用上段提到的生成 a 序列的方式,会出现很多重复,很难容斥,不利于计算本质不同的数量,我们换个思路,哪些序列是可以被 A 生成的。
一个任意的序列 b,我看它能否被 A 生成,只需要从左到右,每一个数寻找第一个大于等于它的数。既然能保证它是合法的,那就让匹配的位置尽量靠左以腾出更多的数进行匹配,这个贪心是显然正确的。那么 a 序列的集合就是所有合法的 b 的集合,我们 dp 求出在这个贪心下可以存活多少本质不同的 b 序列。
\(f[i][j]\) 表示序列当前匹配到 A 的第 i 个数字,b 的这个位置填 j 的合法 b 序列个数。匹配到 A 的第 i 位,不一定是 b 的第 i 位,因为中间可能空了几个位置,也就是从 A 的位置 k 转移过来,空出来的位置一定满足:\(\forall x\in [k+1,i-1] , A[x]<j\)。同时还要保证 \(A[i]\ge j\),这个位置得填的下 j 。
枚举 A 的每一位,再枚举这一位填哪个数字 j ,上一个位置 k 是由 j 决定的,中间的 x 求和可以用前缀和优化,总复杂度就是 A 数列之和 O(n)。事实上 dp 数组也不需要第二维的 j ,表示成和就好了。k 是怎么由 j 决定的呢,用一个 lst 数组记录每一个数字最后一个能放下的位置即可。
最后的最后,记住 A 序列是基于 01 串中有 1 才存在的,纯 0 的输入需要特判。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#include <vector>
#include <queue>
#include <map>
using namespace std;
const ll N=1201010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
const ll p=1000000007;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
ll n, m = -1;
char s[N];
ll a[N];
ll f[N];
ll sum[N];
ll lst[N];
int main() {
scanf("%s",s+1); n = strlen(s+1);
ll now = 0;
for(ll i=1;i<=n+1;i++) {
if(i==n+1 || s[i]=='1') {
a[++m] = now;
now = 0;
}
if(s[i]=='0') {
now++;
}
}
m--;
if(m==-1) {
cout<<n<<"\n";
return 0;
}
f[0] = sum[0] = 1;
for(ll i=1;i<=m;i++) {
for(ll j=0;j<=a[i];j++) {
(f[i] += sum[i-1] - ((lst[j]>0) ? sum[lst[j]-1] : 0) + p) %= p;
lst[j] = i;
}
sum[i] = (sum[i-1] + f[i]) %p;
}
cout<<((sum[m]*(a[0]+1)%p*(a[m+1]+1)%p)%p+p)%p;
return 0;
}