HDU 6155 Subsequence Count(dp+线段树)

本文介绍了一种高效算法,针对01串进行反转区间操作,并快速计算特定区间内不同子序列的数量。通过动态规划和矩阵运算,结合线段树优化,解决了大规模数据集下的性能瓶颈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出一个长度为n01s,两种操作:

1.反转区间[l,r],即把0变成1,把1变成0

2.询问区间[l,r]中不同的子序列个数

Input

第一行输入一整数T表示用例组数,之后输入两个整数n,q分别表示串长和操作数,之后输入一个长度为n01串,最后q行每行一种操作(1T5,1n,q105)

Output

对于每个2操作,输出不同子序列个数,结果模109+7

Sample Input

2
4 4
1010
2 1 4
2 2 4
1 2 3
2 1 4
4 4
0000
1 1 2
1 2 3
1 3 4
2 1 4

Sample Output

11
6
8
10

Solution

先不考虑反转操作,对于串s1s2...sn,以dp[i][0/1]表示以第i位结尾的不同的子序列个数

如果si=0,以第i位结尾的子序列有三种,只有第i位的0,末尾0超过1个,末尾0只有一个,故有转移dp[i][0]=dp[i1][0]+dp[i1][1]+1,dp[i][1]=dp[i1][1]

如果si=1,同理可得dp[i][0]=dp[i1][0],dp[i][1]=dp[i1][0]+dp[i1][1]+1

A=111010001B=100111001

则如果si=0,则有(dp[i][0]  dp[i][1]  1)=(dp[i1][0]  dp[i1][1]  1)A

如果si=1,则有(dp[i][0]  dp[i][1]  1)=(dp[i1][0]  dp[i1][1]  1)B

而注意到AB只差交换前两行和前两列,令P=010100001为初等矩阵,注意到P1=P,故有A=PBP,B=PAP

现在考虑反转操作,对于区间[l,r],每个位置对应一个转移矩阵(A/B),这些转移矩阵的乘积即为这个区间的转移矩阵,令这些转移矩阵为Cl,...,Cr,反转后转移矩阵为Dl,...,Dr,则有Di=PCiP,进而DlDl+1...Dr=PClCl+1...CrP,即反转后的区间转移矩阵即为反转前区间转移矩阵交换前两行和前两列,用线段树维护区间转移矩阵的乘积即可,每次修改不需要把该区间转移矩阵重新乘一遍,只需将之前的转移矩阵交换前两行前两列即可,这里一个小优化是转移矩阵前两行最后一个数字均为0,不需要交换,这样只需进行5次交换即可实现转移矩阵交换前两行前两列的操作

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
#define maxn 100005
#define mod 1000000007
#define ls (t<<1)
#define rs ((t<<1)|1)
typedef int Mat[3][3];
int T,n,q;
char s[maxn]; 
Mat m[maxn<<2],A[2]={{1,0,0,1,1,0,1,0,1},{1,1,0,0,1,0,0,1,1}},ans;
bool Lazy[maxn<<2];
void mul(Mat a,Mat b,Mat &c)
{
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
        {
            ll temp=0;
            for(int k=0;k<3;k++)temp+=(ll)a[i][k]*b[k][j];
            c[i][j]=temp%mod;
        }
}
void deal(int t)
{
    swap(m[t][0][0],m[t][0][1]);
    swap(m[t][1][0],m[t][1][1]);
    swap(m[t][2][0],m[t][2][1]);
    swap(m[t][0][0],m[t][1][0]);
    swap(m[t][0][1],m[t][1][1]);
}
void build(int l,int r,int t)
{
    Lazy[t]=0;
    if(l==r)
    {
        for(int i=0;i<3;i++)
            for(int j=0;j<3;j++)
                m[t][i][j]=A[s[l]-'0'][i][j];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,ls);build(mid+1,r,rs);
    mul(m[ls],m[rs],m[t]);
}
void push_down(int t)
{
    if(Lazy[t])
    {
        Lazy[t]=0;
        Lazy[ls]^=1,Lazy[rs]^=1;
        deal(ls),deal(rs);
    }
}
void update(int L,int R,int l,int r,int t)
{
    if(L<=l&&r<=R)
    {
        Lazy[t]^=1;
        deal(t);
        return ;
    }
    push_down(t);
    int mid=(l+r)/2;
    if(L<=mid)update(L,R,l,mid,ls);
    if(R>mid)update(L,R,mid+1,r,rs);
    mul(m[ls],m[rs],m[t]);
}
void query(int L,int R,int l,int r,int t)
{
    if(L<=l&&r<=R)
    {
        Mat temp;
        mul(ans,m[t],temp);
        for(int i=0;i<3;i++)
            for(int j=0;j<3;j++)
                ans[i][j]=temp[i][j];
        return ;
    }
    push_down(t);
    int mid=(l+r)/2;
    if(L<=mid)query(L,R,l,mid,ls);
    if(R>mid)query(L,R,mid+1,r,rs);
    mul(m[ls],m[rs],m[t]);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&q);
        scanf("%s",s+1);
        build(1,n,1);
        while(q--)
        {
            int op,l,r;
            scanf("%d%d%d",&op,&l,&r);
            if(op==1)update(l,r,1,n,1);
            else
            {
                memset(ans,0,sizeof(ans));
                ans[0][0]=ans[1][1]=ans[2][2]=1;
                query(l,r,1,n,1);
                printf("%d\n",(ans[2][0]+ans[2][1])%mod);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值