As Easy As Possible 倍增法

As we know, the NTU Final PK contest usually tends to be pretty hard. Many teams got frustrated when participating NTU Final PK contest. So I decide to make the first problem as “easy” as possible. But how to know how easy is a problem? To make our life easier, we just consider how easy is a string.


Here, we introduce a sane definition of “easiness”. The easiness of a string is the maximum times of “easy” as a subsequence of it. For example, the easiness of “eeaseyaesasyy” is 2. Since “easyeasy” is a subsequence of it, but “easyeasyeasy” is too easy.


How to calculate easiness seems to be very easy. So here is a string s consists of only ‘e’, ‘a’, ‘s’, and ‘y’. Please answer m queries. The i-th query is a interval [li , ri ], and please calculate the easiness of s[li ..ri ]. 


Input


The first line contains a string s. The second line contains an integer m. Each of following m lines contains two integers li
, ri .
• 1 ≤ |s| ≤ 105
• 1 ≤ m ≤ 105
• 1 ≤ li ≤ ri ≤ |s|
• s consists of only ‘e’, ‘a’, ‘s’, and ‘y’


Output


For each query, please output the easiness of that substring in one line.


Sample input 1


easy
3
1 4
2 4
1 3


Sample output 1


1
0
0


Sample input 2


eeaseyaesasyy
4
1 13
2 12
2 10
3 11


Sample output 2


2
2
1

0


题意:给出一个只含有'e' ,'a','s' ,'y' 四种字符的字符串,询问m次区间[ Li, Ri ]的子序列中形如easyeasy的字符串最多重复easy多少次。

我的做法是对于每一个位置先倒序处理一遍得到每一个'e'后面的第一个'a'的位置,每一个'a'后面的第一个's'的位置等等,还有每个字符后第一个'e'的位置,

再求出每个字符沿着'e','a','s','y'的循环往后走2^n步会到达的位置,然后先在给定区间内找到第一个'e'的位置再用倍增法往后移动统计走过了多少个easy,直到往后走4步就会越界,就暴力往后走3步看会不会越界,如果不会越界则统计数加1。

因为用倍增法走2^n(n>2)步后所在的位置的字符还是'e',所以可以直接统计出答案。

时间复杂度:O(nlogn)


队友的做法是分块,每一个块储存有多少个'easy'子序列,每个块第一个'easy'的子序列前一段是有否有'y','sy','asy'这样的子序列,每个块的最后一个'easy'子序列后是否有'e','ea','eas'这样的子序列,这样就可以完成块与块之间的转移。时间复杂度O(n*sqrt(n)),时限1s,也可以过这题。

我的代码:

#include <bits/stdc++.h>
using namespace std;

inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}


inline void read(long long  &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void write(int x){
    static const int maxlen=100;
    static char s[maxlen];
        if (x<0) {   putchar('-'); x=-x;}
    if(!x){ putchar('0'); return; }
    int len=0; for(;x;x/=10) s[len++]=x % 10+'0';
    for(int i=len-1;i>=0;--i) putchar(s[i]);
}


inline void write(long long x){
    static const int maxlen=100;
    static char s[maxlen];
        if (x<0) {   putchar('-'); x=-x;}
    if(!x){ putchar('0'); return; }
    int len=0; for(;x;x/=10) s[len++]=x % 10+'0';
    for(int i=len-1;i>=0;--i) putchar(s[i]);
}


const int maxn=120000;
char s[maxn];
int nex[maxn][22];
int st[maxn];
int now[22];
int a[maxn];
int n;

int get_num( char ch){
if (ch=='e')
    return 0;
if (ch=='a')
    return 1;
if (ch=='s')
    return 2;
if (ch=='y');
    return 3;
}


void doit(int l,int r){
int x=l;
int nowlen=20;
int ans=0;
x=st[ l ];
if ( (x>r)|| (x==0) )
    {
        puts("0");
        return ;
    }

for (;;)
    {
        while ( ( nowlen>0 ) && (  (  ( nex[x][nowlen]==0 ) || ( nex[x][nowlen]>r ) ) ) )
            nowlen--;
        if ( nowlen < 2 )
            break;
        ans+=( 1<<( nowlen-2 ) );
        x=nex[x][nowlen];
    }
int cnt=0;
while (  (nex[x][0]<=r)&& (nex[x][0]!=0) )
    {
        x=nex[x][0];
        cnt++;
    }
if (cnt==3)
    ans++;
printf("%d\n",ans);
}

int main(){
    scanf("%s",s);
    n=strlen(s);
    for (int i=0;i<n;i++)
        a[i+1]=get_num( s[i]);
    for (int i=n;i>=1;i--)
        {
            nex[i][0]=now[ (a[i]+1)%4 ];
            now[ a[i] ]=i;
            st[ i ]=now[0];
        }
    for (int i=1;i<=20;i++)
        for (int j=1;j<=n;j++)
            nex[ j ][ i ]=nex[ nex[ j ][ i-1] ] [ i-1 ];
    int m;
    read(m);
    for (int i=1;i<=m;i++)
        {
            int l,r;
            read(l); read(r);
            int tot=0;
            doit( l , r);
        }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值