HDU 6156 Palindrome Function 经典数位DP

HDU 6156 Palindrome Function

原题连接
http://acm.hdu.edu.cn/showproblem.php?pid=6156

这是一个经典的数位 dp
i=LRj=lrf(i,j)=j=lri=LRf(i,j)
我们记:
slove(n,k)=i=1nf(i,k)
则:
j=lri=LRf(i,j)=j=lr(slove(R,j)slove(L1,j))
通过数位 dp ,可以快速 O(logkN) 计算 slove(N,k)
记含有前导 0 的长度为len k 进制回文数字数量为:
dp[k][len]
令: dp[k][1]=k,dp[k][0]=1
则: dp[k][len]=dp[k][len2]k , len>1
记不含有前导 0 的长度为len k 进制回文数字数量为:

P[k][len]

P[k][0]=0,P[k][1]=k1
P[k][len]=dp[k][len2](k1) 这是因为两端可选范围是 [1,k1]
记不含有前导 0 长度不大于len k 进制回文数字的数量为

D[k][len]

D[k][len]=i=0lenP[k][i]
ktn<kt+1
记。含有前导 0 的不超过n的。 k 进制回文数的数量为

DFS(n,k,t)

这里。 t 是用来确定ktn<kt+1
u=nkt
对于最高位确定。意味着最低位也取相同数字。
所以一次确定两个数字。
最高位取 [0,u1] 时。中间数字可以随意取。都不会超过 n 的范围。

这部分对答案的贡献为:

dp[k][t1]u

对于最高位为 u 时.

因为
uktn<(u+1)kt

我们记最高位为 u 时。中间数字可以取到的最大值为:(除去最高最低位)

r=n mod ktk[n mod k<u]

此时我们可以得到一个子问题:
DFS(n,k,t)=dp[k][t1]u+DFS(r,k,t2)
特别的:
DFS(n,k,0)=n+1DFS(n,k,1)=nk+[nkn mod k]
现在我们解决 slove(n,k)
ktn<kt+1
u=nkt
因为不能含有前导 0 .幸运的是。我们已经计算了小于kt的所有不含前导0的回文数的数量。
D[k][t]
对于最高位取 [1,u1] 时。这部分贡献位:
dp[k][t1](u1)
对于最高位取 u 时。中间一部分的取值最大值为:
r=n mod ktk[n mod k<u]
所以:
slove(n,k)=D[k][t]+dp[k][t1](u1)+DFS(r,k,t2)
特别的。
n<kslove(n,k)=nn<k2slove(n,k)=k+nk+[nkn mod k]2
搞定。
下面是代码:
#include <algorithm>
#include <string.h>
#include <stdio.h>
#define MAXN 100

using namespace std;
typedef long long LL;
const LL inf=40*1e9;

LL dp[MAXN][MAXN];
LL D[MAXN][MAXN];
LL bit[MAXN][MAXN];
int size[MAXN];
void init();

LL DFS(LL n,int k,int b)
{
    if(b==0)return n+1;
    if(b==1)return n/k+(n/k<=n%k);
    LL u=n/bit[k][b];
    LL r=n%bit[k][b];
    LL ans=u*dp[k][b-1];
    r/=k;
    if(u>n%k)r--;
    return ans+DFS(r,k,b-2);
}

LL slove(LL n,int k)//小于等于n的k进制数回文数的个数
{
    if(n<(LL)k)   return n;
    if(n<((LL)k*k)) return k-1+n/k+(n/k<=n%k)-1;
    for(int i=size[k];i>-1;i--)
        if(n>=bit[k][i])
        {
            LL u=n/bit[k][i];
            LL ans=D[k][i]+(u-1)*dp[k][i-1];
            LL r=(n%bit[k][i])/k;
            if(n%k<u)r--;
            return ans+DFS(r,k,i-2);
        }
    return 0;
}

int main ()
{
    init();
    int t,deep=1;
    scanf("%d",&t);
    while(t--)
    {
        int L,R,l,r;
        scanf("%d %d %d %d",&L,&R,&l,&r);
        LL ans=0;
        L--;
        for(int i=l;i<=r;i++)
        {
            LL cnt=slove((LL)R,i)-slove((LL)L,i);
            ans+=cnt*i;
            ans+=R-L-cnt;
        }
        printf("Case #%d: %lld\n",deep++,ans);
    }
    return 0;
}

void init()
{
    for(int i=2;i<37;i++)
    {
        bit[i][0]=1;
        while(bit[i][size[i]]<=inf)
        {
            size[i]++;
            bit[i][size[i]]=bit[i][size[i]-1]*(LL)i;
        }
    }
    for(int i=2;i<37;i++)
    {
        dp[i][0]=1;
        dp[i][1]=i;
        D[i][0]=1;
        D[i][1]=i-1;
        for(int j=2;j<=size[i];j++)
        {
            dp[i][j]=dp[i][j-2]*i;
            D[i][j]=D[i][j-1]+dp[i][j-2]*(i-1);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值