题意

  给定一个 \(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. C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)

    1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...

  2. Git-T

    或在命令行上创建一个新的存储库echo“#gittest”>> README.md git init git add README.md git commit -m“first commi ...

  3. Django视图之FBV与CBV

    一. CBV与FBV CBV:Class Based View FBV:Function Based View 我们之前写过的都是基于函数的view,就叫FBV.还可以把view写成基于类的,那就是C ...

  4. 通俗易懂的lambda表达式,不懂来找我!

    lambda是Python编程语言中使用频率较高的一个关键字.那么,什么是lambda?它有哪些用法?网上的文章汗牛充栋,可是把这个讲透的文章却不多.这里,我们通过阅读各方资料,总结了关于Python ...

  5. 【图像-视频处理】YUV420、YV12与RGB24的转换公式

    bool YV12ToBGR24_Native(unsigned char* pYUV,unsigned char* pBGR24,int width,int height) { if (width ...

  6. 【Linux开发】linux设备驱动归纳总结(三):5.阻塞型IO实现

    linux设备驱动归纳总结(三):5.阻塞型IO实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. Java小知识----List复制:浅拷贝与深拷贝

    原文地址: https://blog.csdn.net/demonliuhui/article/details/54572908 List浅拷贝 众所周知,list本质上是数组,而数组的是以地址的形式 ...

  8. 设计模式:建造者模式(Builder)

    流水作业大家应该都清楚吧!在流水作业中,我们可以将一些复杂的东西给构建出来,例如汽车.我们都知道汽车内部构件比较复杂,由很多部件组成,例如车轮.车门.发动机.方向盘等等,对于我们用户来说我们并不需要知 ...

  9. 3的倍数 或运算构造x(牛客第四场)-- triples I

    题意: 给你一个数,希望你能用最少的3的倍数或运算成它,让你输出答案. 思路: 进制%3有规律,1.2.4.8.16%3是1.2.1.2.1 ... 利用这一点分情况取一些位合成一些数就是答案了. # ...

  10. Python_3day

    循环 循环是一种控制语句块重复执行的结构 while 适用于广度遍历 for 开发中经常使用   while 循环 当一个条件保持真的时候while循环重复执行语句 while 循环一定要有结束条件, ...