hdu6156 Palindrome Function 题解(数位DP 或 打表)

本文探讨了一个关于数位的函数f(a,b),并利用数位DP技术求解特定范围内所有回文数的求和问题。通过详细解析算法思路,包括记忆化搜索、递归转移和二分查找等技巧,展示了如何高效地解决复杂数学问题。

请注意:一定要看到最后!

原题链接:
HDU

题意简述

定义一个关于数位的函数 f ( a , b ) = f(a,b)= f(a,b)=
{ 1 ( 当 a 在 b 进 制 下 不 是 回 文 数 ) b ( 别 的 情 况 ) \begin{cases} 1 \qquad(当a在b进制下不是回文数)\\ b \qquad(别的情况) \end{cases} {1(ab)b()
∑ b a s e = l r ∑ j = L R f ( j , b a s e ) \sum\limits_{base=l}^{r}\sum\limits_{j=L}^{R}f(j,base) base=lrj=LRf(j,base)
(偷偷改了一下。。。)

数据

输入

多组数据。先有一个 T T T表示 T T T组数据,然后是 T T T行每行四个正整数 L , R , l , r L,R,l,r L,R,l,r,表示式子中的参数 L , R , l , r L,R,l,r L,R,l,r 1 &lt; = l &lt; = r &lt; = 36 , 1 &lt; = L &lt; = R &lt; = 1 0 9 1&lt;=l&lt;=r&lt;=\red{36},1&lt;=L&lt;=R&lt;=10^9 1<=l<=r<=36,1<=L<=R<=109

输出

T T T行。每行有 1 1 1个正整数,表示第 T T T组数据的答案。输出格式:
C a s e ( 空 格 ) # i : ( 空 格 ) a n s ( 换 行 ) Case(空格)\#i:(空格)ans(换行) Case()#i:()ans()

"Case #%d: %d\n"

其中 i i i表示第 i i i组数据, a n s ans ans是第 i i i组数据的答案。

样例

输入
3
1 1 2 36
1 982180 10 10
496690841 524639270 5 20
输出
Case #1: 665
Case #2: 1000000
Case #3: 447525746

思路

在这里顶一下fuzhihong巨佬的这篇博客,写的十分详细,我是靠这个看懂的!

(恕我直言,别的博客都写的啥玩意。。。说要用数位 D P DP DP,珂是没有说具体咋 D P DP DP,我这个蒟蒻,显然看不懂啊。。。)

这个题。。首先我在写题面的时候将原来的式子作了变形,也就是调换了一下 ∑ \sum 的位置。

其次,注意到 l , r l,r l,r的范围很小,所以 l , r l,r l,r这一层珂以暴力枚举。当然,不同的进制之间几乎没有规律,所以也没有办法优化掉这一层。
回到原问题。如果我们当前枚举到了 b a s e base base进制,我们知道这个进制里 L , R L,R L,R间有 c n t cnt cnt个回文数,那么后面的和就是
c n t ∗ b a s e + ( R − L + 1 − c n t ) cnt*base+(R-L+1-cnt) cntbase+(RL+1cnt)
其中 c n t ∗ b a s e cnt*base cntbase是所有回文数给出的贡献, ( R − L + 1 − c n t ) (R-L+1-cnt) (RL+1cnt)是所有不回文的数给出的贡献。 L , R L,R L,R之间的回文数个数,珂以用 [ 1 , R ] [1,R] [1,R]之间减去 [ 1 , L − 1 ] [1,L-1] [1,L1]之间。现在的问题就是:

b a s e base base进制下 [ 1 , x ] [1,x] [1,x]之间有多少回文

这就非常毒瘤了。看起来,珂以。。。数位 D P DP DP?

仿佛是的,那就试试看⑧。先写出了这样的函数:

void DFS(bool lim,int base,int start,int pos)
{
	
}

b a s e base base(表示进制), p o s pos pos(表示当前位置),还有 l i m lim lim(是否选择了 x x x的前缀)是本题中显然需要的元素。这两个不确定,就相当于啥都不确定。接下来我们会发现,前导 0 0 0是不参与回文计算的。也就是说, 00100 00100 00100这样的不是回文数。所以我们也要记录我们是从哪里开始的,用 s t a r t start start表示。

那么,如何保证我们算出来的是回文呢?显然是不能一位一位判的,用fuzhihong巨佬的话说,就玩完了。所以我们要一边搜一边判,显然需要记录我们选了的位是哪些。接下来分三种情况转移:

  1. 选择了一位 0 0 0(即出现了前导 0 0 0):
    注意前导 0 0 0是不参与回文计算的。所以 s t a r t start start − 1 -1 1。当然我们当前考虑的位 p o s pos pos也要 − 1 -1 1,然后继续去寻找答案。要注意 l i m lim lim的限制。
  2. 当前的位置超过了对称中心(即 ( s t a r t + 1 ) / 2 (start+1)/2 (start+1)/2
    此时就要去和前面选过的位进行比较了。此时由于超过了对称中心,就需要和前面作比较。如果当前枚举的新位 i i i和前面的位不对称,那么就要去找不回文的答案了。那么,不回文有答案么?显然是没有的。比如说,我们枚举到了一个 113 1 2 ∗ ∗ 113\red{1}2** 11312,显然,后面有啥都不珂能回文了。所以就直接返回 0 0 0好了。由此珂见,我们需要多传一个参数进来,叫 i s _ p a l i n is\_palin is_palin,表示当前是否回文。
  3. 别的情况
    记录好 n o w now now数组, p o s − 1 pos-1 pos1(即到下一个位置),继续搜索。

当然,我们是记忆化搜索,所以要知道啥时候能用记忆化的结果。如果没有限制条件,而且 d p dp dp(即记忆化数组)上面有值了,就直接返回 d p dp dp中记录的值。 而且也不能忘了每次算完记录一下。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    int d[60];int cnt=0;
    void decompose(int base,int x)
    {
        cnt=0;
        while(x)
        {
            d[cnt++]=x%base;
            x/=base;
        }
        d[cnt]=0;
    }

    int dp[60][60][60][2];
    int now[60];
    int DFS(bool lim,bool is_palin,int base,int start,int pos)
    {
        if (!is_palin) return 0;//直接退出
        if (pos<0)
        {
            return 1;//此时is_palin是1
            //所以肯定有一个解(长度为0是一种解)
        }
        if (!lim and ~dp[start][pos][base][is_palin])
        {
            return dp[start][pos][base][is_palin];
        }//记忆化

        int ans=0;
        int up=lim?d[pos]:base-1;//注意限制
        for(int i=0;i<=up;++i)
        {
            now[pos]=i;//记录选择的数组
            if (start==pos and i==0)
            {
                ans+=DFS(lim and i==up,is_palin,base,start-1,pos-1);
            }
            else if ((start-1)/2>=pos and is_palin)
            {
                ans+=DFS(lim and i==up,now[start-pos]==i,base,start,pos-1);
            }
            else
            {
                ans+=DFS(lim and i==up,is_palin,base,start,pos-1);
            }//三种情况
        }
        if (!lim)
        {
            dp[start][pos][base][is_palin]=ans;
        }
        return ans;
    }
    int calc(int base,int x)
    {
        memset(now,-1,sizeof(now));
        decompose(base,x);
        return DFS(1,1,base,cnt-1,cnt-1);
    }
    int l,r,L,R;
    void Solve(int Case)
    {
        int ans=0;
        for(int base=l;base<=r;++base)
        {
            int cnt=calc(base,R)-calc(base,L-1);
            ans+=cnt*base+(R-L+1-cnt);
        }
        printf("Case #%lld: %lld\n",Case,ans);
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        int t;scanf("%lld",&t);
        memset(dp,-1,sizeof(dp));
        for(int Case=1;Case<=t;++Case)
        {
            scanf("%lld%lld%lld%lld",&L,&R,&l,&r);
            Solve(Case);
        }
    }
    #undef int //long long
};
main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

你以为这就结束了?不!

还有一个瞎搞过题法:打表后二分答案。

我们会发现,确定一个回文数,只需要确定一半,另一半直接 拼 \red{拼} 出来。比如在十进制中,我们枚举 123 123 123,就珂以确定两个回文数 12321 12321 12321 123321 123321 123321。所以,这就告诉我们,在 1 e 9 1e9 1e9以内,回文数的个数大概就是开个根左右(位数枚举一半)。。。即空间是 1 e 5 ∗ 36 ∗ 一 些 常 数 1e5*36*一些常数 1e536。经过我的多次试验,这个常数 = 1.5 =1.5 =1.5就过了。

给出这个代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    int palin[37][300100];
    //每个数会确定两个,所以这里开三倍
    int pcnt[37];
    int d[70];
    void Add1(int base,int x)//第一种情况,即中间重合一部分的情况,长度为奇数
    {
        int cnt=0;memset(d,0,sizeof(d));
        while(x)
        {
            d[++cnt]=x%base;
            x/=base;
        }reverse(d+1,d+cnt+1);
        for(int i=cnt+1;i<=2*cnt-1;++i)
        {
            d[i]=d[2*cnt-i];
        }

        int newx=0;
        for(int i=1;i<=2*cnt-1;++i)
        {
            newx=newx*base+d[i];
        }
        palin[base][++pcnt[base]]=newx;
    }
    void Add2(int base,int x)//第二种情况,直接拼接,长度为偶数
    {
        int cnt=0;
        while(x)
        {
            d[++cnt]=x%base;
            x/=base;
        }reverse(d+1,d+cnt+1);
        for(int i=cnt+1;i<=2*cnt;++i)
        {
            d[i]=d[2*cnt-i+1];
        }

        int newx=0;
        for(int i=1;i<=2*cnt;++i)
        {
            newx=newx*base+d[i];
        }
        palin[base][++pcnt[base]]=newx;
    }
    void Init()
    {
        memset(pcnt,0,sizeof(pcnt));
        for(int base=2;base<=36;++base)
        {
            for(int i=1;i<=150000;++i)//1.5倍常数
            {
                Add1(base,i);
                Add2(base,i);//两种情况
            }
            sort(palin[base]+1,palin[base]+pcnt[base]+1);//方便upper_bound
        }
    }

    int calc(int base,int x)
    {
        int pos=upper_bound(palin[base]+1,palin[base]+pcnt[base]+1,x)-1-palin[base];
        return pos;
    }//直接找
    int l,r,L,R;
    void Soviet()
    {
        int ans=0;
        for(int base=l;base<=r;++base)
        {
            int cnt=calc(base,R)-calc(base,L-1);
            ans+=cnt*base+(R-L+1-cnt);
        }
        printf("%lld\n",ans);
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Init();
        int t;scanf("%lld",&t);
        for(int i=1;i<=t;++i)
        {
            scanf("%lld%lld%lld%lld",&L,&R,&l,&r);
            printf("Case #%lld: ",i);
            Soviet();
        }
    }
    #undef int //long long
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值