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 转移过来,空出来的位置一定满足:x[k+1,i1],A[x]<j。同时还要保证 A[i]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; }

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/18359774.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示