【分治】[2016"百度之星" - 初赛(Astar Round2A)]BD String

题目

Problem Description
众所周知,度度熊喜欢的字符只有两个:B和D。

今天,它发明了一种用B和D组成字符串的规则:

S(1)=B

S(2)=BBD

S(3)=BBDBBDD

S(n)=S(n−1)+B+reverse(flip(S(n−1))

其中,reverse(s)指将字符串翻转,比如reverse(BBD)=DBB,flip(s)指将字符串中的B替换为D,D替换为B,比如flip(BBD)=DDB。

虽然度度熊平常只用它的电脑玩连连看,这丝毫不妨碍这台机器无与伦比的运算速度,目前它已经算出了S(21000)的内容,但度度熊毕竟只是只熊,一次读不完这么长的字符串。它现在想知道,这个字符串的第L位(从1开始)到第R位,含有的B的个数是多少?

Input
第一行一个整数T,表示T(1≤T≤1000) 组数据。

每组数据包含两个数L和R(1≤L≤R≤1018) 。

Output
对于每组数据,输出S(21000)表示的字符串的第L位到第R位中B的个数。

Sample Input
3
1 3
1 7
4 8

Sample Output
2
4
3

分析

其实 S(61) 的长度已经超过 1018 了,不要去理 21000
根据题目给出的构建字符串的规则,我们可以想到这很像是在构建一棵完全二叉树:
每次新建一个节点作为根,当前的树作为根的左子树,然后将左子树翻折到右边成为右子树,将右子树的B,D取反。
这个字符串就是这棵树的中序遍历序列。
我们令 pointi 为第i次所构建的树的根的编号, sumi 表示第i次所构建的树中 B 的数量,即S(i)这个序列中的 B 的数量,很像一个前缀和,而且长度的区间是倍增的。
point,sum的值预处理出来。
对于每次询问 [L,R] ,可以把其分为3部分:

pL=min(i) pointi>=LpR=max(i) pointi<=R[L,R]=[L,pointpL)[pointpL,pointpR](pointpL,R]

sum[pointpL,pointpR]=sumpR1+1sumpL1

然后递归计算第一个区间,和第三个区间在其所在子树的根的左子树中所对应部分中 B 的个数,用区间总字符数减去它,因为前缀和的区间是倍增的,所以左子树的区间分得更“细”,这个就很像线段树中的求和。
sum(pointpR,R)]=RpointpRsum[pointpR2R,pR1]

pL>pR
sum[L,R]=RL+1sum[pointpR2R,pointpR2L]

所以时间复杂度和线段树类似,每次询问问 O(log2n)

代码

#include<cstdio>
#include<algorithm>
using namespace std;
template<class T>
void Read(T &x){
    char c;
    bool f=0;
    while(c=getchar(),c!=EOF){
        if(c=='-')
            f=1;
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            if(f)
                x=-x;
            return;
        }
    }
}
long long sum[100],point[100];
long long dfs(long long l,long long r){
    if(l>r)
        return 0;
    int pl=lower_bound(point+1,point+60+1,l)-point,pr=lower_bound(point+1,point+60+1,r)-point;
    if(point[pr]>r)
        pr--;
    if(pl>pr)
        return r-l+1-dfs(2*point[pr]-r,2*point[pr]-l);
    return sum[pr-1]+1-sum[pl-1]+dfs(l,point[pl]-1)+(r-point[pr]-dfs(point[pr]-r+point[pr],point[pr]-1));
}
void prepare(){
    point[1]=1;
    sum[1]=1;
    int i;
    for(i=2;i<=60;i++){
        point[i]=point[i-1]*2;
        sum[i]=sum[i-1]+1+point[i]-1-sum[i-1];
    }
    point[61]=point[60]*2;
}
long long T;
int main()
{
    prepare();
    Read(T);
    long long l,r;
    while(T--){
        Read(l),Read(r);
        printf("%I64d\n",dfs(l,r));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值