题解 CF589C【Polycarp's Masterpiece】

$\text{Link}$

题意

给你一个字符串 $s$,有 $n$ 次操作,每次操作将当前字符串 $s$ 复制,并将后 $k_i$ 个字符放到字符串最前面,接到当前字符串之后,接下来 $m$ 次查询,每次查询问最终字符串 $[l,r]$ 区间中有多少个字符 $c$。

$|s|,k_i\le100,1\le n,m\le 10^5,1\le l\le r\le 10^{18}$

思路

校内模拟赛 $\text C$ 题,赛时做出来了!

不知道为什么这场 $\text{CF}$ 好像被删了,交上去会 $\text{UKE}$。

这应该是到目前为止这道题的第三种做法,另外两种做法可以参考 @ezoiHQM 的可持久化 $\text{FHQ}$ 和 @dqstz 的可持久化线段树。

这里我提供一种比较好想、好写,但是依赖于 $k$ 的范围的做法。

我们知道,经过 $i$ 次操作,$s$ 的长度就会变为 $l\times 2^i$($l$ 为 $s$ 的初始长度),进行 $10^5$ 次操作是完全没有意义的,因为查询只到 $10^{18}$,所以进行 $60$ 次操作就完全够了($2^{60}>10^{18}$)。

首先思考如何求出位置 $p$ 的字符。考虑一次操作之后的字符串长这样:

getchar

考虑设计函数 $\text{getch}(p,d)$ 表示求第 $p$ 位置的字符,当前正处理第 $d$ 次操作。

  • 若 $d=0$,则直接返回 $s_p$;
  • 若 $d\ne0$,
    • 若 $p$ 落在区间 $[A,B]$ 之间,则调用 $\text{getch}(p,d-1)$ 进行递归处理。
    • 若 $p$ 落在区间 $B'$ 中,找到其在 $B$ 中对应的位置,递归即可。考虑 $B'$ 的结束位置为 $y=l\times2^{d-1}+k_d$,则调用函数 $\text{getch}(l\times2^{d-1}-y+p,d-1)$;
    • 若 $p$ 落在区间 $A'$ 中,找到其在 $A$ 中对应的位置,递归即可。考虑 $B'$ 的结束位置为 $y=l\times2^{d-1}+k_d$,则调用函数 $\text{getch}(p-y,d-1)$。

每次递归都会将 $d$ 减 $1$,由于 $d=O(\log p)$,所以一次 $\text{getch}(p,d)$ 的调用消费为 $O(\log p)$。

考虑将每次询问差分成 $r$ 的前缀和减去 $l-1$ 的前缀和,对于一次前缀和询问,记其为 $\text{ask}(r,d,x)$ 表示询问 $[1,r]$ 区间中 $x$ 的出现次数,当前正在处理第 $d$ 次操作。还是那张图:

ask

  • 若 $d=0$,考虑预处理出原字符串中每个字符出现次数的前缀和,则可以 $O(1)$ 查询;
  • 若 $d\ne0$,
    • 若 $r$ 落在区间 $[A,B]$ 之间,则返回 $\text{ask}(r,d-1,x)$;
    • 若 $r$ 落在区间 $B'$ 中,贡献分为 $[A,B]$、$[B'_l,r]$ 两部分,其中第一部分贡献可以通过预处理 $s$ 中每个字母的出现次数 $O(1)$ 计算,对于第二部分,由于 $k$ 很小,可以通过预处理每次的 $B$ 部分的字母出现次数的前缀和 $O(1)$ 计算,直接返回;
    • 若 $r$ 落在区间 $A'$ 中,贡献分为 $[A,B]$、$B'$ 和 $[A'_l,r]$ 三部分,前两部分直接按照上一种情况处理,对于第三部分,找到其在 $A$ 中对应的位置,递归询问即可。考虑 $B'$ 的结束位置为 $y=l\times2^{d-1}+k_d$,则返回前两部分的答案和加上 $\text{ask}(r-y,d-1,x)$。

同样,由于 $d=O(\log r)$,调用一次 $\text{ask}(r,d,x)$ 的调用消费为 $O(\log r)$。

总时间复杂度为 $O(\sum k\log r(\log r+|\sum|)+m\log r)$。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e5+10;
char str[110];
int l,n,m;
int pr[26][110],k[N],pre[26][70][110];
/*
pre[i][j][k]=第 j 天后 k_j 个字符中前 k 个字符中有多少个 i 
由于询问区间知道 10^18,则只需开 26x\log2(10^18)x100 的数组即可
对于 1 天,将其分为三段:左半段,循环段,平移段,分类讨论 r 落在哪段 
O(m\log r)
*/
vector<ll>s,t;
inline int getch(ll p,int d){
    if(d==0)
        return str[p]-'a';
    if(p<s[d])
        return getch(p,d-1);
    ll yiw=s[d]+k[d]-1;
    if(p<=yiw)
        return getch(t[d-1]-yiw+p,d-1);
    return getch(p-yiw,d-1); 
}
inline ll ask(ll r,int d,int x){
    if(d==0)
        return pr[x][r];
    if(s[d]>r)
        return ask(r,d-1,x);
    ll yiw=s[d]+k[d]-1;
    ll ad=(1ll<<d-1)*pr[x][l];
    if(r<=yiw)
        return pre[x][d][r-t[d-1]]+ad;
    return ask(r-yiw,d-1,x)+pre[x][d][k[d]]+ad;
}
inline ll ask(ll r,int x){
    if(r==0)
        return 0;
    int i;
    for(i=0;t[i]<r;i++);
    return ask(r,i,x);
}
int main(){
//  freopen("data_C1.in","r",stdin);
//  freopen("data_C1.out","w",stdout);
    l=readstr(str);
    n=read(),m=read();
    for(int i=1;i<=l;i++)
        for(int j=0;j<26;j++)
            pr[j][i]=pr[j][i-1]+(str[i]==(j+'a'));
    s.push_back(1),t.push_back(l);
    for(int i=1;i<=n;i++)
        k[i]=read();
    for(int i=1;t.back()<1e18&&i<=60;i++){
        ll x=t.back();
        s.push_back(x+1),t.push_back(x*2);
    }
    for(int i=1;i<=60&&i<t.size();i++){
        k[i]%=t[i-1];
        for(int j=1;j<=k[i];j++){
            int ch=getch(t[i-1]-k[i]+j,i-1); 
            for(int c=0;c<26;c++)
                pre[c][i][j]=pre[c][i][j-1]+(c==ch);
        }
    }
    while(m--){
        ll l=read(),r=read(),x=getc()-'a';
        write(ask(r,x)-ask(l-1,x)),putc('\n');
    }
    flush();
}

再见 qwq~

posted @ 2021-08-06 19:55  ffffyc  阅读(14)  评论(0)    收藏  举报  来源