[HDU6155]Subsequence Count 线段树+矩阵优化DP
题目链接
DP方程不难推:
状态设为dp[i][0]和dp[i][1],表示从第一位到这一位以1/0结尾的序列数是多少。
假如当前位是1,那么
最后的+1是考虑只有一个1的序列。一开始我觉得这样不能保存已经计算出来的序列,后来发现,当一个序列结尾加上1变成一个新序列,这个序列确实没了,但之前产生这个序列的序列会继续产生这个序列。这里比较难理解,可以手动模拟一下。
假如当前位是0,和1同理。
但如果每次都从l到r做dp,复杂度过高,根据经验,可以用矩阵+线段树来降低这一过程的时间复杂度。
如果当前位是1,那么就表示为
如果当前位是0,那么就表示为
我们就有
事实上我这里推错了,应该把dp放在左边,然后转移矩阵放右边。不然线段树处理,就是从r乘到l了。为了纪念这个困扰我几天的错误,我决定在博客里把它写出来...
虽然这样对不起读者但也能让读者在知道思路的情况下自己尝试推导正确的转移矩阵。
然后如何处理1和0的互相转换呢,不难发现两种转移矩阵想要互相变换,只需要左乘一个初等方阵交换1,2列,右乘一个初等方阵交换1,2行,对于区间的矩阵全部转换,每个矩阵都左乘右乘,然后通过矩阵乘法的结合律,化简为:
因为矩阵乘法具有结合律,通过带lazyTag的线段树不难实现这样的一个矩阵区间乘法和查询问题。
代码结构很明确,先定义Matrix类,定义乘法,然后设几个矩阵常数,然后是线段树。
非AC代码:
#include<iostream>
#include<cstdio>
#define maxn 100005
using namespace std;
int T, N, Q ,type;
char s[maxn];
const int type1[3][3] = {{1, 0, 0}, {1, 1, 1}, {0, 0, 1}};
const int type0[3][3] = {{1, 1, 1}, {0, 1, 0}, {0, 0, 1}};
const int trans[3][3] = {{0, 1, 0}, {1, 0, 0}, {0, 0, 1}};
struct Matrix{
int arr[3][3];
//type2
Matrix operator * (Matrix a){
Matrix ans;
for (int i = 0; i < 3;i++)
for (int j = 0; j <= 3;j++){
int cnt = 0;
for (int k = 0; k < 3;k++)
cnt += arr[i][k] * a.arr[k][j];
ans.arr[i][j] = cnt;
}
return ans;
}
void emplace(){
for (int i = 0; i < 3;i++)
for (int j = 0; j < 3;j++)
if(i==j)
arr[i][j] = 1;
else
arr[i][j] = 0;
}
void init(int type){
if(type==1)
for (int i = 0; i < 3;i++)
for (int j = 0; j < 3;j++)
arr[i][j] = type1[i][j];
if(type==0)
for (int i = 0; i < 3;i++)
for (int j = 0; j < 3;j++)
arr[i][j] = type0[i][j];
if(type==2)
for (int i = 0; i < 3;i++)
for (int j = 0; j < 3;j++)
arr[i][j] = trans[i][j];
}
}Trans;
struct node{
int l, r;
Matrix num,tag;
} tree[maxn << 2];
void push_up(int rt){
tree[rt].num = tree[rt << 1].num * tree[rt << 1 | 1].num;
}
void push_down(int rt){
tree[rt << 1].tag = tree[rt << 1 | 1].tag = tree[rt].tag;
tree[rt << 1].num = tree[rt << 1].tag * tree[rt << 1].num * tree[rt << 1].tag;
tree[rt << 1 | 1].num = tree[rt << 1 | 1].tag * tree[rt << 1 | 1].num * tree[rt << 1 | 1].tag;
tree[rt].tag.emplace();
}
void build(int rt,int l,int r){
tree[rt].l = l;
tree[rt].r = r;
if(l==r){
if(s[l]=='1')
tree[rt].num.init(1);
if(s[l]=='0')
tree[rt].num.init(0);
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
push_up(rt);
}
void update(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r){
tree[rt].num = Trans * tree[rt].num * Trans;
tree[rt].tag = tree[rt].tag * Trans;
return;
}
push_down(rt);
int mid = tree[rt].l + tree[rt].r >> 1;
if(l<=mid)
update(rt << 1, l, r);
if(r>mid)
update(rt << 1 | 1, l, r);
push_up(rt);
}
Matrix query(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)
return tree[rt].num;
push_down(rt);
Matrix ans;
ans.emplace();
int mid = tree[rt].l + tree[rt].r >> 1;
if(l<=mid)
ans = ans * query(rt << 1, l, r);
if(r>mid)
ans = ans * query(rt << 1 | 1, l, r);
return ans;
}
void output(Matrix ans){
int ansn = 1;
for (int i = 0; i < 3;i++)
ansn *= ans.arr[1][i];
printf("%d\n", ansn);
}
int main(){
scanf("%d",&T);
Trans.init(2);
while(T--){
scanf("%d%d", &N,&Q);
scanf("%s", s + 1);
build(1, 1, N);
int l, r;
while(Q--){
scanf("%d%d%d", &type, &l, &r);
if(type==1)
update(1, l, r);
else
output(query(1, l, r));
}
}
}
分类:
解题报告
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人