题目传送门
题目大意: 问1~m中有多少个数满足
。
题解
仔细观察这个转移方程——
发现这里面含有大量的2和4出现,于是想到了神奇的二进制,这里先给出结论——
的值相当于将i的二进制数反转之后得到的二进制数的十进制。
比如
的二进制为
,反转去掉前导零后就变成了
,也就是说
。
下面给出十分不严谨接地气的证明。
证明
分开讨论每一个转移方程。
(1) f[2n]=f[n]
显然,2n的二进制只是在n的二进制后面加了个0,它的二进制反转之后与n的二进制反转是一样的,所以他们 值相等。
(2) f[4n+1]=2f[2n+1]-f[n]
我们设
的二进制为
,那么
的二进制就是
,
的二进制就是
,于是再把
列成竖式:
减
——————
显然 就是 的二进制反转,即
(3) f[4n+1]=3f[2n+1]-2f[n]
与(2)同理。具体过程可以自己模拟一下。
证毕
于是现在题目就变成了:在1~m中,有多少个数的二进制反转等于自己。换句话说,就是有多少个数的二进制是回文的。
一看这个m这么大,显然就要用数位dp了(下文都将m作为二进制数)。
设 表示 位的二进制数中有多少个数是回文的(这个 是dp用的数组,上面那个题目中的f下面将不再提到)。
因为一个二进制数的最高位一定是1,所以只要使它的最低位也是1,中间填个回文数即可使这个数是回文数,所以得到方程 ,乘2是因为 是指长度为i-2的最高位是1的回文数的数量,但是假如只是插个长度为i-2的数到长度为i的二进制数中的话,最高位是0也没有关系,于是这里要乘2。
长度小于m的长度 的 二进制回文数的个数直接利用 统计即可,对于长度与m相同的,dfs一下即可。dfs的步骤详见程序:
#include <cstdio>
#include <cstring>
int n;
int maxx(int x,int y){return x>y?x:y;}
struct node{
int a[1010],len;
node(){for(int i=0;i<=1009;i++)a[i]=0;len=1;}
friend node operator+(const node &a,const node &b)//由于答案是高精度,所以套个高精度板子
{
node c;c.len=maxx(a.len,b.len);
for(int i=1;i<=c.len;i++)
{
c.a[i]+=a.a[i]+b.a[i];
if(c.a[i]>9)
{
c.a[i+1]+=c.a[i]/10;
c.a[i]%=10;
if(i==c.len)c.len++;
}
}
return c;
}
};
node f[1010],p[1010];//p[i]=2^i
int a[1010],t=0;
void work()
{
f[0].a[1]=f[1].a[1]=f[2].a[1]=1;
for(int i=3;i<=t;i++)
f[i]=f[i-2]+f[i-2];
p[0].a[1]=1;p[1].a[1]=2;
for(int i=2;i<=t;i++)
p[i]=p[i-1]+p[i-1];
}
node yi,ans;
void die(int x){ans=ans+p[(t-(t-x+1)*2)/2+t%2];}
int ka[1010];
//dfs是在尝试构造一个位数与m相同的比m小的回文二进制数
void dfs(int x)//表示dfs到第x位
{
//构造方法:先枚举前一半,再判断后一半在与前一半回文时,这个数是否大于m
if(x>t/2)//前一半
{
if(t%2==1&&x==t/2+1)//特判一下m是奇数位时,中间的那一位
{
if(a[x]==1)ans=ans+yi;//假如在m中这一位是1,那么如果把这一位搞成0,那么后面无论是什么,这个数都一定小于m,于是直接+1
dfs(x-1);//这一位与m的这一位相同的情况
return;
}
else
{
if(a[x]==1)
{
if(x!=t)die(x);//假如m的这一位是1,可以考虑把它搞成0,那么后面就可以乱填数,贡献的回文数数量就是die(x)
//因为第x~t位都已经确定了,所以后面对应的那几位也已经确定了,die就是负责统计中间可以填多少个回文数
ka[x]=1;dfs(x-1);//考虑这一位与m的这一位相同的情况
}
else ka[x]=0,dfs(x-1);//别无选择,直接向下搜
}
}
else
{
for(int i=x;i>=1;i--)//判断后半段是否可以与前半段构成回文并且不大于m
{
int y=t-i+1;
if(a[i]>=ka[y])
{
if(a[i]>ka[y])
{
ans=ans+yi;
return;
}
}
else return;
}
ans=ans+yi;
return;
}
}
void count()
{
if(t==1&&a[t]==1)ans.a[1]=1;//注意要特判一下m=1的情况
else
{
for(int i=1;i<t;i++)
ans=ans+f[i];
dfs(t);
}
}
void print()
{
for(int i=ans.len;i>=1;i--)
printf("%d",ans.a[i]);
}
char s[1010];
int b[1010];
void input()
{
scanf("%s",s);
int m=strlen(s);
for(int i=1;i<=m;i++)
b[i]=s[m-i]-'0';
while(m>1||b[1]>0)//将m转为二进制存在a中
{
a[++t]=b[1]%2;
int last=0;
for(int i=m;i>=1;i--)
{
int la=b[i]%2*5;
b[i]/=2;
b[i]+=last;
last=la;
}
if(b[m]==0)m--;
}
yi.a[1]=1;
}
int main()
{
input();
work();
count();
print();
}