HNOI 2002 Kathy函数 题解

题目传送门

题目大意: 问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 10,于是再把 2 f [ 2 n + 1 ] − f [ n ] 2f[2n+1]-f[n] 2f[2n+1]f[n] 列成竖式:
        1 − 0 1-0 10
减           − -
——————
        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[i2]×2,乘 2 2 2 是因为 f [ i − 2 ] f[i-2] f[i2] 是指长度为 i − 2 i-2 i2 的最高位是 1 1 1 的回文数的数量,但是假如只是插个长度为 i − 2 i-2 i2 的数到长度为 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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值