题目大意: 问1~m中有多少个数满足 f [ i ] = i f[i]=i f[i]=i。
题解
仔细观察这个转移方程——
f
[
1
]
=
1
f[1]=1
f[1]=1
f
[
3
]
=
3
f[3]=3
f[3]=3
f
[
2
n
]
=
f
[
n
]
f[2n]=f[n]
f[2n]=f[n]
f
[
4
n
+
1
]
=
2
f
[
2
n
+
1
]
−
f
[
n
]
f[4n+1]=2f[2n+1]-f[n]
f[4n+1]=2f[2n+1]−f[n]
f
[
4
n
+
3
]
=
3
f
[
2
n
+
1
]
−
2
f
[
n
]
f[4n+3]=3f[2n+1]-2f[n]
f[4n+3]=3f[2n+1]−2f[n]
发现这里面含有大量的 2 2 2 和 4 4 4 出现,于是想到了神奇的二进制,这里先给出结论—— f [ i ] f[i] f[i] 的值相当于将 i i i 的二进制数反转之后的数。
比如 100 100 100 的二进制为 1100100 1100100 1100100,反转去掉前导零后就变成了 10011 10011 10011,也就是说 f [ 100 ] = 1001 1 ( 2 ) = 1 9 ( 10 ) f[100]=10011_{(2)}=19_{(10)} f[100]=10011(2)=19(10)。
证明
分开讨论每一个转移方程。
(1) f[2n]=f[n]
显然, 2 n 2n 2n 的二进制只是在 n n n 的二进制后面加了个 0 0 0,它的二进制反转之后与 n n n 的二进制反转是一样的,所以他们 f f f 值相等。
(2) f[4n+1]=2f[2n+1]-f[n]
我们设
f
[
n
]
f[n]
f[n] 的二进制为
−
-
−,那么
f
[
2
n
+
1
]
f[2n+1]
f[2n+1] 的二进制就是
1
−
1-
1−,
2
f
[
2
n
+
1
]
2f[2n+1]
2f[2n+1] 的二进制就是
1
−
0
1-0
1−0,于是再把
2
f
[
2
n
+
1
]
−
f
[
n
]
2f[2n+1]-f[n]
2f[2n+1]−f[n] 列成竖式:
1
−
0
1-0
1−0
减
−
-
−
——————
1
1
1
0
0
0
−
-
−
显然 10 − 10- 10− 就是 4 n + 1 4n+1 4n+1 的二进制反转,即 f [ 4 n + 1 ] f[4n+1] f[4n+1]
(3) f[4n+1]=3f[2n+1]-2f[n]
与 ( 2 ) (2) (2) 同理。具体过程可以自己模拟一下。
证毕
于是现在题目就变成了:在 1 1 1 ~ m m m 中,有多少个数的二进制反转等于自己。换句话说,就是有多少个数的二进制是回文的。
一看这个 m m m 这么大,显然就要用数位 d p dp dp 了(下文都将 m m m 作为二进制数)。
设 f [ i ] f[i] f[i] 表示 i i i 位的二进制数中有多少个数是回文的(这个 f f f 是 d p dp dp 用的数组,上面那个题目中的 f f f 下面将不再提到)。
因为一个二进制数的最高位一定是 1 1 1,所以只要使它的最低位也是 1 1 1,中间填个回文数即可使这个数是回文数,所以得到方程 f [ i ] = f [ i − 2 ] × 2 f[i]=f[i-2]\times 2 f[i]=f[i−2]×2,乘 2 2 2 是因为 f [ i − 2 ] f[i-2] f[i−2] 是指长度为 i − 2 i-2 i−2 的最高位是 1 1 1 的回文数的数量,但是假如只是插个长度为 i − 2 i-2 i−2 的数到长度为 i i i 的二进制数中的话,最高位是 0 0 0 也没有关系,于是这里要乘 2 2 2。
长度小于 m m m 的长度 的 二进制回文数的个数直接利用 f f f 统计即可,对于长度与 m m m 相同的, d f s dfs 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();
}