[莫队维护DP] LOJ#6074. 「2017 山东一轮集训 Day6」子序列

博客探讨了如何利用动态规划(DP)解决数列不同子序列个数的问题,介绍了两种DP思路,包括初始的直接DP方法及其缺点,以及转换后的DP方式,即fi,j表示以字符i开头,字符j结尾的子序列个数。通过添加字符并利用莫队算法进行维护,解决了复杂度问题。博主在实践中遇到了卡常问题,最终学习并应用了wxh的莫队算法优化技巧,成功解决了问题。" 125457991,14262738,修改MySQL存储函数,"['数据库管理', 'SQL', '函数']

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

如果只考虑求一个数列的不同子序列个数,可以简单地DP出来

fi=2×fi1flstai1,其中 lstj 表示字符 j 上次出现的位置,没出现过就忽略

但是这样不好维护

可以换一种DP方式,fi,j 表示以字符 i 开头,字符 j 结尾的子序列个数。

那么在左边新加一个字符 xfx,i 会加上 jxfj,i ,考虑以 x 开头的子序列,去掉开头的所有连续的 x 后的子序列个数就是 jxfj,i ,然后这些子序列在开头加一些 x 是原本的 fx,i ,在加上当前的 x 就都是未出现过的了…

这话说的我自己都看不懂

大概就这样可以用莫队维护

然后就被卡常了……

学了一下wxh的莫队打法

    friend bool operator <(const QQ &a,const QQ &b){
        return pos[a.l]<pos[b.l] || (pos[a.l]==pos[b.l] && (pos[a.l]&1 ? a.r<b.r : a.r>b.r));
    }

然后就过了…

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <ctime>

using namespace std;

typedef long long ll;

const int N=100010,P=1e9+7;

int n,q,S;
ll b[15][15];
int a[N];
int ans[N];
int pos[N];

struct QQ{
    int l,r,g;
    friend bool operator <(const QQ &a,const QQ &b){
        return pos[a.l]<pos[b.l] || (pos[a.l]==pos[b.l] && (pos[a.l]&1?a.r<b.r:a.r>b.r));
    }
}Q[N];

ll cur;

inline void add(ll &x,ll y){ 
    (x+=y)%=P;
}

inline void Addr(const int &x){
    for(int i=1;i<=9;i++){
        int now=(b[i][0]-b[i][x]+(i==x));
        cur+=now; add(b[i][x],now); add(b[0][x],now); add(b[i][0],now);
    }
    cur%=P;
}

inline void Addl(const int &x){
    for(int i=1;i<=9;i++){
        int now=(b[0][i]-b[x][i]+(i==x));
        cur+=now; add(b[x][i],now); add(b[x][0],now); add(b[0][i],now);
    }
    cur%=P;
}

inline void Minusl(const int &x){
    for(int i=1;i<=9;i++){
        int now=-(b[0][i]-b[x][i]+(i==x));
        cur+=now; add(b[x][i],now); add(b[x][0],now); add(b[0][i],now);
    }
    cur%=P;
}

inline void Minusr(const int &x){
    for(int i=1;i<=9;i++){
        int now=-(b[i][0]-b[i][x]+(i==x));
        cur+=now; add(b[i][x],now); add(b[0][x],now); add(b[i][0],now);
    }
    cur%=P;
}

inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline int rea(int *a){
    char c=nc(); int len=0;
    for(;c>'z'||c<'a';c=nc());for(;c>='a'&&c<='z';a[++len]=c-'a'+1,c=nc()); return len;
}

inline void rea(int &x){
    char c=nc(); x=0;
    for(;c>'9'||c<'0';c=nc());for(;c>='0'&&c<='9';x=x*10+c-'0',c=nc());
}

int main(){
    S=sqrt(n=rea(a));
    rea(q);
    for(int i=1;i<=n;i++) pos[i]=i/S;
    for(int i=1;i<=q;i++)
        rea(Q[i].l),rea(Q[i].r),Q[i].g=i;
    sort(Q+1,Q+1+q);
    int l=1,r=0;
    for(int i=1;i<=q;i++){
        while(r<Q[i].r) Addr(a[++r]);
        while(r>Q[i].r) Minusr(a[r--]);
        while(l<Q[i].l) Minusl(a[l++]);
        while(l>Q[i].l) Addl(a[--l]);
        ans[Q[i].g]=(cur+P)%P;
    } 
    for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值