H
贡献法
考虑计算01串中每一位对答案的贡献并求和:
由于每个连续段的贡献只有1,故可设定一个连续段的贡献是由这个连续段的第一个数字造成的。那么计算某一位的贡献,就只需要让这个数字成为某个连续段的开头即可。
具体地,假设计算第\(i\)位且\(s[i]=0\)。要计算这一位对答案的贡献,则要让这一位作为所在连续段的开头,那么这一位紧左边的连续的若干0就不能加入到当前子序列中,直到遇到 左边最近的1 或者 空。而这一位右边的任意位的选取是没有限制的,因为已经规定了贡献是开头引起的,因此紧后面即使仍有连续若干0,计算的仍是该位的贡献,没有影响。这样就可以用式子来表示每一位的贡献了:
第 \(i\) 位的贡献(设\(s[i]=0\))式子为:
其中加1是考虑\(s[i]\)左侧无数字的情况;乘号左边为第 \(i\) 位左侧所有1的位置,对于任意 \(j<i且s[j]=1\),都可以作为当前子序列中\(s[i]\)最左侧的字符,那么\(s[j]\)左侧的字符就可以随意选取了(因为有\(s[j]=1\)隔着\(s[i]=0\),就保证了不会让\(s[i]\)的贡献消失,那么\(s[j]\)左侧的任意位就没有约束);而乘号右侧对应\(s[i]\)右侧任意位也是可随意选取的。
\(O(n)\)预处理即可计算当前字符串的答案。现在考虑翻转某个字符后如何快速计算新的贡献:
\(s[i]\)由 \(0\) 变为 \(1\),则\(s[i]\)这一位本身一定有影响;根据式子可知\(s[i]\)的修改不会对\(s[i]\)左侧每一位的贡献有影响(证明略),而会对\(s[i]\)右侧每一位的贡献有影响 —— 对于任意 \(j \in [i + 1,n]\),\(s[j]=0\)时,\(s[j]\)左侧的1的数量增加了(第\(i\)位的影响),而\(s[j]=1\)时,\(s[j]\)左侧的0的数量减少了(也是第\(i\)位的影响)。因此还需要考虑后缀贡献的变化。
考虑用什么数据结构维护整个过程:计算初始式子,需要求每一位相同数字的\(2^{j}\)的前缀和;而修改时考虑后缀的贡献变化,也需要维护相同数字的\(2^{j}\)的后缀和。而由于涉及到修改字符,前缀和与后缀和是在动态变化的,因此树状数组是最合适的维护动态前后缀和的数据结构。具体细节见代码。
code