题意

  给定一个 \(01\) 串 \(S_{1...n}\) 和 \(Q\) 个操作。

  操作有 \(2\) 种类型:

    1. 将 \([l,r]\) 区间所有数取反(\(0→1,\space 1→0\))

    2. 询问字符串 \(S\) 的子串 \(S_{l...r}\) 有多少个不同的子序列,答案模 \(10^9+7\)。

  在数学中,某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。

  \(n,q\le 10^5\)

题解

  注意子序列的定义,不是子串!!!

  如果是子串的话这题还是挺神仙的,但注意到是子序列后就会发现是个强行二合一(?)的题……

  

  考虑朴素 \(\text{dp}\),设 \(dp(i,j)\) 表示 \(S_{1...i}\) 以 \(j(j∈{0,1})\) 结尾的不同子序列数量(下面统一省略“不同”二字)

  设 \(S_i\) 为 \(k\),则转移为 $$dp(i,k) = 2dp(i-1,k) + dp(i-1,k\text{ ^ } 1) + 1 - dp(i-1,k)$$ $$dp(i,k\text{ ^ } 1) = dp(i-1,k\text{ ^ } 1)$$  解释一下 \(dp(i,k)\) 的转移中每一项的含义:

    $ + dp(i-1,k)$ 表示累加 \(S_{1...i-1}\) 中以 \(k\) 为结尾的子序列数量;

    $ + dp(i-1,k) + dp(i-1,k\text{ ^ } 1$ 表示在 \(S_{1...i-1}\) 中以 \(k\) 为结尾的子序列后接一个 \(k\) 得到的子序列的数量;

    $ + 1$ 是指新增一个长度为 \(i\) 的子序列 \(S_{1...i}\)。

    $ - dp(i-1,k)$ 表示第二种情况所统计的子序列 可能与 \(S_{1...i-1}\) 中的子序列重复,我们需要去掉重复统计的子序列。由于重复统计的串都是以 \(S_i=k\) 为结尾,所以重复数量就是 \(dp(i-1,k)\)。

  \(dp(i,k\text{ ^ } 1)\) 的转移就很好理解了,就是继承 \(S_{1...i-1}\) 中以 \(k\text{ ^ } 1\) 结尾的子序列数量,\(k\) 不能作为任何子序列的结尾。

  显然 $$dp(i,k) = 2dp(i-1,k) + dp(i-1,k\text{ ^ } 1) + 1 - dp(i-1,k)$$   可以简化为 $$dp(i,k) = dp(i-1,k) + dp(i-1,k\text{ ^ } 1) + 1$$

  

  然后发现这个 \(\text{dp}\) 被放到了线段树上,再观察一下 \(\text{dp}\) 式,大概就知道要把它表示成线性递推形式了。

  先把 \(\text{dp}\) 的第一维去掉,一会我们将优化这一维。

  令初始矩阵为一个列数为 \(3\) 的行向量,第一项表示 \(dp(0)\),第二项表示 \(dp(1)\),第三项表示常数 \(1\)(就是每次从 \(dp(i-1,k)\) 转移到 \(dp(i,k)\) 时加的那个 \(1\))。

  不难发现,初始矩阵为 \(\left[\begin{matrix}0 & 0 & 0\end{matrix}\right]\),若 \(S_i=0\) 则第 \(i\) 位的转移矩阵为 \(\left[\begin{matrix}1 & 0 & 0\\1 & 1 & 0\\1 & 0 & 1\end{matrix}\right]\),若 \(S_i=1\) 则第 \(i\) 位的转移矩阵为 \(\left[\begin{matrix}1 & 1 & 0\\0 & 1 & 0\\0 & 1 & 1\end{matrix}\right]\)。

  放到线段树上维护区间内所有转移矩阵的乘积就好了。

  

  至此解决了不带修改的子任务。下面考虑修改,即区间取反对区间内转移矩阵造成的影响。

  认(xia)真(ji)分(ba)析(cai)后,发现对于最简单的情况,\(S_i=0\) 的转移矩阵变成了 \(S_i=1\) 的转移矩阵,反之亦然。

  好像就是把转移矩阵的第 \(1,2\) 行交换,再把第 \(1,2\) 列交换?(先交换列再交换行的效果一样,证明的话直接考虑每个数的行列变化情况即可)

  确实是这样的,直接在线段树上把对应区间的转移矩阵换一下两行两列就行了。

  这样做你就漏了严谨性:怎么证明任意多个转移矩阵乘起来(包括两种矩阵混乘),在对区间取反时也是把它交换两行两列呢?

  证明:

    有一种常见的矩阵,叫初等矩阵。

    设一个初等矩阵 \(E\) 为单位矩阵 \(I\) 交换第 \(i,j\) 行,则一个矩阵左乘 \(E\),会交换第 \(i,j\) 行。

    设一个初等矩阵 \(E\) 为单位矩阵 \(I\) 交换第 \(i,j\) 列,则一个矩阵右乘 \(E\),会交换第 \(i,j\) 列。

    (其实单位矩阵交换 \(i,j\) 两行等于交换 \(i,j\) 两列)

    对于一个矩阵 \(F\),若 \(F^2=I\),则 \(F=F^{-1}\)。

    所以设本题中初始转移矩阵为 \(A\),区间取反后交换两行两列得到的转移矩阵为 \(B\),则 \(E\times A\times E = E\times A\times E^{-1} = B\)。

    把若干个矩阵左右乘 \(E\) 后依次右乘,会发现每对相邻的 \(E^{-1}\) 和 \(E\) 乘起来得到了 \(I\)(\(E\times E^{-1} = E^{-1}\times E = 1\)),最后得到了 \(E\times 这若干个矩阵依次右乘\times E^{-1}\),其答案就是“这若干个矩阵依次右乘”得到的矩阵交换两行两列。

    Q.E.D

小优化

  初始矩阵和转移矩阵可以不对常数 \(1\) 开那一维。

  令初始矩阵为一个列数为 \(3\) 的行向量,第一项表示 \(dp(0)+1\),第二项表示 \(dp(1)+1\)。

  则初始矩阵为 \(\left[\begin{matrix}1 & 1\end{matrix}\right]\),若 \(S_i=0\) 则第 \(i\) 位的转移矩阵是 \(\left[\begin{matrix}1 & 0\\1 & 1\end{matrix}\right]\),若 \(S_i=1\) 则第 \(i\) 位的转移矩阵是 \(\left[\begin{matrix}1 & 1\\0 & 1\end{matrix}\right]\)。

  最后把矩阵的两项之和 \(-2\) 就是答案了。

  这个优化比较神仙,把常数改了也能用。比如把转移的常数 \(1\) 改成 \(2\),把行向量表示的东西分别改成 \(dp(0)+2,dp(1)+2\) 即可,最后答案 \(-4\)。总之就是要保证所有变量加的常数相同。

拓展

  这个性质可以用于一些矩阵快速幂的优化。

  考虑对一个矩阵求 \(A^n\)。我们尝试构造一个矩阵 \(\phi\) 和一个能 \(O(n)\) 求快速幂的矩阵 \(B\)(比如对角线矩阵),使得 \(\phi\times B\times \phi^{-1} = A\)。

  由于 \((\phi\times B\times \phi^{-1})^n = \phi\times B\times \phi^{-1}\times \phi\times B\times \phi^{-1}\times \phi\times ...\times \phi^{-1} = \phi\times B^n\times \phi^{-1}\)(其实跟本题同理,每对相邻的 \(\phi^{-1}\) 和 \(\phi\) 乘起来得到了 \(I\)”),所以我们直接 \(O(n)\) 求出 \(B^n\) 即可,左右乘 \(\phi\) 就得到了 \(A^n\)。

  这样能降低一些矩阵快速幂的复杂度,比如【CF 947E】,这题构造出 \(\phi,B\) 矩阵后,由于矩阵很特殊,用 \(\text{FFT}\) 做矩乘可以把复杂度做到 \(O(n\log n)\)。当然这个做法不是通用的,因为存在一组符合要求的矩阵 \(\phi,B\) 的题目并不多。

  而且肉眼并不容易构造出 \(\phi,B\) 的矩阵,详情请咨询 scb 大佬(雾)

#include<bits/stdc++.h>
#define N 100010
#define mod 1000000007
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x; return 0-x;
}
struct Matrix{
int x[2][2];
Matrix(){x[0][0]=x[1][1]=x[0][1]=x[1][0]=0;}
Matrix(int a, int b){x[0][0]=a, x[0][1]=b, x[1][0]=x[1][1]=0;}
Matrix(int a, int b, int c, int d){x[0][0]=a, x[0][1]=b, x[1][0]=c, x[1][1]=d;}
inline void change(){swap(x[0][0],x[1][1]), swap(x[0][1],x[1][0]);}
inline int* operator [](int p){
return x[p];
}
Matrix operator * (Matrix y)const{
Matrix ret=Matrix();
for(int i=0; i<2; ++i)
for(int k=0; k<2; ++k)
if(x[i][k])
for(int j=0; j<2; ++j)
ret[i][j] = (ret[i][j] + 1ll * x[i][k] * y[k][j] % mod) % mod;
return ret;
}
/*
inline bool operator != (const Matrix &y){
return x[0][0]!=y[0][0] || x[0][1]!=y[0][1] || x[1][0]!=y[1][0] || x[1][1]!=y[1][1];
}*/
}I,chushi,zhuanyi[2];
int n,q;
char s[N];
namespace SegTree{
#define ls o<<1
#define rs o<<1|1
struct Tree{Matrix sum; bool tag;}tr[N<<2];
inline void pushup(int o){
tr[o].sum=tr[ls].sum*tr[rs].sum;
}
void pushdown(int o){
if(tr[o].tag){
tr[ls].sum.change(), tr[rs].sum.change();
tr[ls].tag^=1, tr[rs].tag^=1;
tr[o].tag=0;
}
}
void build(int o, int l, int r){
if(l==r){tr[o].sum=zhuanyi[s[l]-'0']; return;}
int mid=l+r>>1;
build(ls,l,mid), build(rs,mid+1,r);
pushup(o);
}
void mdf(int o, int l, int r, int L, int R){
if(L<=l && r<=R){
tr[o].sum.change();
tr[o].tag^=1;
return;
}
int mid=l+r>>1;
pushdown(o);
if(L<=mid) mdf(ls,l,mid,L,R);
if(R>mid) mdf(rs,mid+1,r,L,R);
pushup(o);
}
Matrix query(int o, int l, int r, int L, int R){
if(L<=l && r<=R) return tr[o].sum;
int mid=l+r>>1; Matrix ret=I;
pushdown(o);
if(L<=mid) ret = ret * query(ls,l,mid,L,R);
if(R>mid) ret = ret * query(rs,mid+1,r,L,R);
return ret;
}
inline int query(int l, int r){
Matrix tmp=chushi*query(1,1,n,l,r);
//cout<<tmp[0][0]<<' '<<tmp[0][1]<<' '<<tmp[1][0]<<' '<<tmp[1][1]<<endl;
//tmp=chushi*tmp;
return (0ll+tmp[0][0]+tmp[0][1]-2+mod)%mod;
}
inline void mdf(int l, int r){
mdf(1,1,n,l,r);
}
#undef ls
#undef rs
}using namespace SegTree;
int main(){
I=Matrix(1,0,0,1), chushi=Matrix(1,1), zhuanyi[0]=Matrix(1,0,1,1), zhuanyi[1]=Matrix(1,1,0,1);
n=read(), q=read();
scanf("%s",s+1);
build(1,1,n);
int type,l,r;
for(int i=1; i<=q; ++i){
type=read(), l=read(), r=read();
if(type==1) mdf(l,r);
else printf("%d\n",query(l,r));
}
return 0;
}

【hdu 6155】Subsequence Count的更多相关文章

  1. 【数位dp】【HDU 3555】【HDU 2089】数位DP入门题

    [HDU  3555]原题直通车: 代码: // 31MS 900K 909 B G++ #include<iostream> #include<cstdio> #includ ...

  2. 【HDU 5647】DZY Loves Connecting(树DP)

    pid=5647">[HDU 5647]DZY Loves Connecting(树DP) DZY Loves Connecting Time Limit: 4000/2000 MS ...

  3. -【线性基】【BZOJ 2460】【BZOJ 2115】【HDU 3949】

    [把三道我做过的线性基题目放在一起总结一下,代码都挺简单,主要就是贪心思想和异或的高斯消元] [然后把网上的讲解归纳一下] 1.线性基: 若干数的线性基是一组数a1,a2,a3...an,其中ax的最 ...

  4. 【HDU 2196】 Computer(树的直径)

    [HDU 2196] Computer(树的直径) 题链http://acm.hdu.edu.cn/showproblem.php?pid=2196 这题可以用树形DP解决,自然也可以用最直观的方法解 ...

  5. 【HDU 2196】 Computer (树形DP)

    [HDU 2196] Computer 题链http://acm.hdu.edu.cn/showproblem.php?pid=2196 刘汝佳<算法竞赛入门经典>P282页留下了这个问题 ...

  6. 【HDU 5145】 NPY and girls(组合+莫队)

    pid=5145">[HDU 5145] NPY and girls(组合+莫队) NPY and girls Time Limit: 8000/4000 MS (Java/Other ...

  7. 【hdu 1043】Eight

    [题目链接]:http://acm.hdu.edu.cn/showproblem.php?pid=1043 [题意] 会给你很多组数据; 让你输出这组数据到目标状态的具体步骤; [题解] 从12345 ...

  8. 【HDU 3068】 最长回文

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=3068 [算法] Manacher算法求最长回文子串 [代码] #include<bits/s ...

  9. 【HDU 4699】 Editor

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=4699 [算法] 维护两个栈,一个栈放光标之前的数,另外一个放光标之后的数 在维护栈的同时求最大前缀 ...

随机推荐

  1. 有效使用Django的QuerySets

    对象关系映射 (ORM) 使得与SQL数据库交互更为简单,不过也被认为效率不高,比原始的SQL要慢. 要有效的使用ORM,意味着需要多少要明白它是如何查询数据库的.本文我将重点介绍如何有效使用 Dja ...

  2. 跨平台python异步回调机制实现和使用方法

    跨平台python异步回调机制实现和使用方法 这篇文章主要介绍了python异步回调机制的实现方法,提供了使用方法代码 1 将下面代码拷贝到一个文件,命名为asyncore.py 代码如下: impo ...

  3. EL表达式与JSTL标签库(二)

    1.JSTL标签库 标签库 作用 URI 前缀 核心 包含Web应用的常见工作,如循环.输入输出等 http://java.sun.com/jsp/jstl/core c 国际化 语言区域.消息.数字 ...

  4. golang 多级json转map

    func main() { jsonStr := `{"isSchemaConforming":true,"schemaVersion":0,"unk ...

  5. 【AMAD】tenacity -- Python中一个专门用来retry的库

    动机 简介 用法 基本用法 何时停止 尝试间的等待 何时retry 其它 热度分析 源码分析 个人评分 动机 很多时候,我们都喜欢为代码加入retry功能.比如oauth验证,有时候网络不太灵,我们希 ...

  6. 《Linux命令行大全》 笔记记录

    1.Shell是什么 2.(文件目录)导航 3.Linux系统 4.操作文件和目录 5.命令的使用 6.重定向 7.透过shell看世界 扩展 引用 8.高级键盘技巧 9.权限 10.进程 11.环境 ...

  7. VirtualBox下Centos6.8网络配置

    win10环境下,VirtualBox和Centos6.8已经按照完毕,下面配置Centos6.8网络. 1.设置VirtualBox为桥接模式,具体的有三种联网方法,我们参考http://www.c ...

  8. 【Python开发】Python 适合大数据量的处理吗?

    Python 适合大数据量的处理吗? python 能处理数据库中百万行级的数据吗? 处理大规模数据时有那些常用的python库,他们有什么优缺点?适用范围如何? 需要澄清两点之后才可以比较全面的看这 ...

  9. 【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存

    linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  10. BP原理 - 前向计算与反向传播实例

    Outline 前向计算 反向传播 很多事情不是需要聪明一点,而是需要耐心一点,踏下心来认真看真的很简单的. 假设有这样一个网络层: 第一层是输入层,包含两个神经元i1 i2和截距b1: 第二层是隐含 ...