题目链接:HDOJ http://acm.hdu.edu.cn/showproblem.php?pid=5694
VJ https://vjudge.net/problem/HDU-5694
方法一 : 分类讨论:
对于任一length>=1的BD串可以分两类:A类满足中点为B,B类满足中点为D
由进行两组操作后结果可知:同一个串的子A,B子串在除再进行二分的对称点外的其他部分相同,所以该题可以二分求解。
二分法代码如下:
代码使用JAVA,和C++基本没区别就不另给c++版本了
import java.util.Scanner;
public class Main {
static long a[]=new long[61];
static long b(long x) {
long ans=0;
int t=2;//t=1为B类,t=2为A类,易知开始递归前所有串均为A类
while(x!=0) {
for(int i=1;i<=60;i++) {
if(a[i]==x) return ans+a[i-1]+1;//刚好剩下一个完整的A,或B类串
else if(a[i]+1==x)return ans+a[i-1]+t;//剩下一个完整的A,或B类串和对称轴
else if(a[i]>x) {//取走A串+对称轴部分,递归剩余部分
ans+=a[i-2]+t;
x=x-a[i-1]-1;
/* 改变字符串的A,B性 */
if(x>a[i-2]) {
t=1;
}
else {
t=2;
}
break;
}
}
}
return ans;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in=new Scanner(System.in);
/*初始化a数组,a[i]表示S(i)的长度,A类:a[i-1]+1,B类:a[i-1]表示S(i)中B的个数*/
a[0]=0;
for(int i=1;i<=60;i++) {
a[i]=a[i-1]*2+1;
}
int t=in.nextInt();
while(t-->0)System.out.println(b(in.nextLong()-1)*-1+b(in.nextLong()));
}
}
方法2:递归
思路:可以用一个函数calc(x),表示从第一位到第x位中B字符的个数,则结果为calc(R)-calc(L),怎么样求calc(x)呢?
用一个数组a[1]~a[60](a[60]>10^18&a[59]<10^18)表示s(1)~s(60)的串长,若x==a[i],则sum=a[i]/2+1;若x<a[i]&x>a[i-1],则对应a[i-1]+1的字符一定为B,那么关于a[i-1]+1对称的两部分a[i-1]+2~x与a[i]-R+1~a[i-1]字符串取反后对称,所以这两部分字符转中B的个数和为R-a[i-1]-1,加上中间对称的B,个数为R-a[i-1],总的B字符个数为R-a[i-1]+calc(a[i]-R),然后递归求解calc(a[i]-R);
代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
long long a[70];
long long calc(long long x)
{
if(x==0) return 0;
long long sum=0;
for(int i=1;i<=60;i++)
{
if(a[i]==x)
return x/2+1;
if(x<a[i])
{
sum+=x-a[i-1];
sum+=calc(a[i]-x);
break;
}
}
return sum;
}
int main()
{
int T;
long long L,R;
scanf("%d",&T);
a[0]=0;
for(int i=1;i<=60;i++)
{
a[i]=a[i-1]*2+1;
}
while(T--)
{
scanf("%I64d%I64d",&L,&R);
printf("%I64d\n",calc(R)-calc(L-1));
}
return 0;
}