[数位dp] upcoj 2223 A-Number and B-Number

题意:

A数列为,包含7或者能被7整除的数从小到大构成。

{a[1]=7,a[2]=14,a[3]=17,a[4]=21,a[5]=27,a[6]=28,a[7]=35,a[8]=37,a[9]=42,a[10]=47};

B数列为,是A数列里的数,但不包含第a[i]个A数列里的数列。

也就是B数列要去掉 a[7],a[14],a[17]这些要去掉。

{b[1]=7,b[2]=14,b[3]=17,b[4]=21,b[5]=27,b[6]=28,b[7]=37,b[8]=42,b[9]=47,b[10]=49};

现在给N,求第N个B数列里的数。

思路:

首先值得肯定的是,我们必须知道A数列,所以数位dp关于A数列。这个都很简单。

dp[i][j][k] 第i位,余数是j,k 代表是否含有了7.

然后就看第N个B数列的数,实际是第几个A数列的数。

我们假设是第N个B 数列的数,是第X个A数列的数。

那么满足N=X-solve(X)   solve(X) 代表1~X中有几个A数列的数。

会发现X-solve(X) 随着X的增大而增大。

所以二分出最小满足X-solve(X)=N 的X。

接着再一个二分求第X个A数列的数是多少就可以了~

最后就是需要注意,题目要求范围是2^63-1,所以开成 unsigned long long。

代码:

#include"stdio.h"
#include"algorithm"
#include"string.h"
#include"map"
#include"iostream"
#include"queue"
#include"string"
#define ll unsigned long long
using namespace std;
ll dp[22][8][2];
int num[22];
ll dfs(int site,int s,int o,int f)
{
    if(site==0)
    {
        if(o==1) return 1;
        if(s==0) return 1;
        return 0;
    }
    if(!f && dp[site][s][o]!=-1) return dp[site][s][o];
    ll ans=0;
    int len=f?num[site]:9;
    for(int i=0; i<=len; i++)
    {
        if(o==1) ans+=dfs(site-1,(s*10+i)%7,1,f&&i==len);
        else
        {
            if(i==7) ans+=dfs(site-1,(s*10+i)%7,1,f&&i==len);
            else ans+=dfs(site-1,(s*10+i)%7,0,f&&i==len);
        }
    }
    if(!f) dp[site][s][o]=ans;
    return ans;
}
ll solve(ll x)
{
    int cnt=0;
    while(x)
    {
        num[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,0,0,1)-1;
}
int main()
{
    ll n;
    memset(dp,-1,sizeof(dp));
    while(scanf("%llu",&n)!=-1)
    {
        ll l=1,r=(1LL<<63)-1,mid,kx;
        while(l<=r)
        {
            mid=(l+r)/2;
            ll tep=mid-solve(mid);
            if(tep>=n)
            {
                kx=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        ll ans;
        l=1,r=(1LL<<63)-1;
        while(l<=r)
        {
            mid=(l+r)/2;
            if(solve(mid)>=kx)
            {
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("%llu\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值