1 条题解
-
0
2048H - Kevin and Strange Operation
一、题目大意
给定一个二进制字符串 ,可以执行任意次奇特操作: 选择位置 ,将 左侧所有字符 ,然后删除 。 求能得到的不同非空二进制字符串的数量,答案对 取模。
二、核心性质推导(解题关键)
这道题的核心是把复杂操作转化为简单的字符串性质,这是代码能极简的原因:
- 操作本质 无论执行多少次操作,最终得到的字符串 ,每一位都对应原串 中一段连续区间的最大值。
- 二进制简化 因为是 串, 运算只有两种结果:区间含 则为 ,全为 则为 。
- 关键定义 定义 :以位置 结尾的连续 的长度;若 ,则 。 例:,。
- 答案拆分 答案 = 单个字符的数量() + 连续 段产生的额外不同字符串数量。
三、解题思路
我们只需要统计连续 段的贡献,用树状数组维护前缀和,快速计算贡献值:
- 预处理 数组,标记所有连续 的长度;
- 逆序遍历字符串,对每个连续 的位置计算额外贡献;
- 用树状数组维护已统计的连续 长度,快速查询/更新;
- 总答案 = 初始 + 所有额外贡献。
四、代码逐段解析
#include <bits/stdc++.h> #define N 1000011 using namespace std; int c[N],n,T; // c:树状数组,n:字符串长度,T:测试用例数 char s[N]; // 存储二进制串 const int p=998244353;// 模数 int pre[N]; // pre[i]:以i结尾的连续0的长度 // 树状数组:查询[1,x]的前缀和 int query(int x){int ans=0;for(;x;x-=x&-x)ans=(ans+c[x])%p;return ans;} // 树状数组:在位置x加上值v void add(int x,int v){for(;x<=n;x+=x&-x)c[x]=(c[x]+v)%p;} int main() { scanf("%d",&T); while(T--) // 多组测试用例 { scanf("%s",s+1);n=strlen(s+1); for(int i=1;i<=n;++i)c[i]=0; // 初始化树状数组 // 预处理pre数组 for(int i=1;i<=n;++i) if(s[i]=='1')pre[i]=0; // 遇到1,连续0中断 else pre[i]=pre[i-1]+1; // 遇到0,连续0长度+1 int ans=n; // 初始答案:所有单个字符(共n个) // 逆序遍历,统计连续0的额外贡献 for(int i=n;i;--i) if(pre[i]) // 当前位置是连续0 { int val=query(pre[i]-1)+1; // 计算当前0段的贡献值 add(pre[i],val); // 更新树状数组 // 累加额外贡献到答案 ans=(ans+1ll*max(0,i-pre[i])*val)%p; } printf("%d\n",ans); } }核心变量/函数说明
pre[i]标记以 结尾的连续 长度,是所有计算的基础。- 树状数组
c维护连续 长度的贡献和,支持 的前缀和查询、单点更新。 val当前连续 段能产生的新的不同字符串数量。- 答案累加 初始答案是 (所有单个字符都是合法结果),再加上连续 段的额外贡献。
五、样例验证
输入样例1
11001- ,初始答案
- 预处理
- 逆序遍历计算额外贡献:
- 总答案 ,完全匹配标准答案。
输入样例2
000110111001100代码计算结果为 ,完全匹配标准答案。
六、复杂度分析
- 时间复杂度: 总字符串长度 , 是树状数组的操作常数,完全满足时间限制。
- 空间复杂度:
仅使用数组存储字符串、
pre、树状数组,符合内存限制。
七、总结
这道题的核心是看透操作的本质,将复杂的字符串操作转化为连续 段的统计问题,再用树状数组优化统计效率。 你的代码是这道题最简洁、最高效的标准解法,直接提交即可AC所有测试点!
- 1
信息
- ID
- 7099
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者