第四届 山东省ACM A-Number and B-Number(数位DP+二分 待整理)

A-Number and B-Number

Time Limit: 1000MS Memory Limit: 65536KB

Problem Description

    Tom is very interested in number problem. Nowadays he is thinking of a problem about A-number and B-number.
    A-number is a positive integer whose decimal form contains 7 or it can be divided by 7. We can write down the first 10 A-number ( a[i] is the ith A-number) 
         {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-number is Sub-sequence of A-number which contains all A-number but a[k] ( that k is a  A-number.)  Like 35, is the 7th A-number and 7 is also an A-number so the 35 ( a[7] ) is not a B-number. We also can write down the first 10 B-number.

         {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};
    Now Given an integer N, please output the Nth B-number

Input

The input consists of multiple test cases.

For each test case, there will be a positive integer N as the description.

Output

For each test case, output an integer indicating the Nth B-number.

You can assume the result will be no more then 2^63-1.

Example Input

17100

Example Output

737470

Hint 

Author

 2013年山东省第四届ACM大学生程序设计竞赛


积累点:


1: (l&r)+((l^r)>>1) == (l+r)/2 
2: 注意判断现在是否有限制。当枚举下一个量时,是(isQuery && j==end),不要搞错。
 


传送门:http://acm.upc.edu.cn/problem.php?id=2223

题意:

能被7整除或者含7的数称为A-Number,所有A-Number从小到大写好,下标编号(从1开始),去掉那些下标为A-Number的数,剩下的数称为B-Number。求第N个B-Number是多少。


思路:


求A-Number就是简单的数位DP。
dp[i][mod] 表示所有i位数中,%7==mod 的数的个数
dp[i][mod] =  (j != 7)  dp[i-1][(mod-(j*10i-1)%7+7)%7]   
                    (j == 7)  10i-1(nowx%10i-1+1)    
                     (j=0~9(end))
之后 二分答案就行了。[0~B]包含  cal(B) - cal(cal(B))  个B-Number。(B包含的ANumber的数目,就是下标最大。这么大的下标范围内有多少ANumber,减掉,剩下就是BNumber的数量)
二分的时候注意二分到最小的那个。就是说。二分的可能是这样
7 7 7 8 8 8
如果查的是8, 则这时候应该二分到第一个8那个位置

参考网址:http://www.cnblogs.com/shinecheng/p/3601235.html

代码:
#include <cstdio>
#include <cstring>

long long dp[20][7];
int num[30];
long long nowx;

long long dfs(int i, int mod, bool isQuery) {
    if (i == 0) {
        return mod == 0;
    }
    long long &nowdp = dp[i][mod];
    if (!isQuery && ~nowdp) {
        return nowdp;
    }
    int end = isQuery?num[i]:9;
    long long ans = 0;
    long long ten = 1;
    for (int k = 0; k < i-1; k++) ten *= 10;

    for (int j = 0; j <= end; j++) {
        if (j == 7) {
            ans += (isQuery&&j==end)?((nowx%ten)+1):ten;  // 这一句要小心。
        } else {
            ans += dfs(i-1, (mod-(j*ten)%7+7)%7, isQuery && j == end);
        }
    }
    if (!isQuery) nowdp = ans;
    return ans;
}

long long cal(long long x) {
    nowx = x;
    int len = 0;
    if (x == 0) return 0;
    while (x) {
        num[++len] = x%10;
        x/=10;
    }
    return dfs(len, 0, true)-1; // 减掉0
}

long long solve(long long number) {
    long long l = 0;
    long long r = 10e19; 
    while (l<r) {
        long long mid = (l&r)+((l^r)>>1);
        long long Anum = cal(mid);
        long long Bnum = Anum - cal(Anum);
        if (Bnum >= number) r = mid;
        else l = mid+1;
    }
    return l;
}
int main(){ 
    long long n;
    memset(dp, -1, sizeof(dp));
    while (scanf("%lld", &n) != EOF) {
        printf("%lld
", solve(n));
    }
}


题目大意: 
A数组就是民间游戏”敲七”的序列 
B数组就是{xxi{A}i{A}} 
然后输出B数组中第n个元素即:Bn

解题思路:

如果直接求第i个元素的话 无论是A数组还是B数组都不能求(反正我不会)

但是他求得是第i个元素 我们可以换一个角度来思考

首先我们可以用一个入门级别的数位dp来统计小于n的元素个数,

那么反过来 [1,n]中 最小的有x个元素的n 就是第x个元素

那么我们就可以用二分答案来统计了

对于A数组我们很好数位dp 
但是对于B数组我们不能直接求出来

但是考虑A与B数组的关系,发现,只要对cal(x)统计的结果ans在统计一下相减即可;

即:

A = cal(x);
B = A - cal(A);
  • 1
  • 2
  • 1
  • 2

注意 
二分溢出的问题 
最大值要(1<<63)-1,

附本题代码 

参考网址:http://blog.csdn.net/qq_33184171/article/details/61441552

#include<bits/stdc++.h>

using namespace std;
typedef long long int LL;
/********************************/

int num[20];
LL dp[20][8][2];

LL dfs(int pos,int mod,int limit,int status){
    if(pos<0) return (status||mod%7==0);
    if(dp[pos][mod][status]!=-1&&!limit) return dp[pos][mod][status]; //忘写 limit  懵逼1000天有没有

    int endi=9;
    if(limit) endi=num[pos];

    LL res = 0;
    for(int i=0;i<=endi;i++)
        res+=dfs(pos-1,(mod*10+i)%7,limit&&i==endi,i==7||status);

    if(!limit) dp[pos][mod][status] = res;
    return res;
}

LL cal(LL x){
    if(!x) return 0;
    int len=0;
    while(x)   num[len++]=x%10,x/=10;
    return dfs(len-1,0,1,0)-1;
}

void solve(LL x){
    LL l=7,r=(1ll<<63)-1,mid,ans=-1;  //r写成1e18 wa懵逼有没有,,,,

    while(l<=r){
        mid=((r-l)>>1)+l;
        LL tmp = cal(mid);
        tmp -= cal(tmp);
        if(tmp>=x)ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld
",ans);
}

int main(){
//    printf("%lld
",~(1ll<<63));
    memset(dp,-1,sizeof(dp));
    LL n;
    while(~scanf("%lld",&n)) solve(n);
    return 0;
}


题意是:给出Anum 的定义.为 包含7或者是该数可以被7整除的数。  a[i]=Anum;

Bnum的定义是,为Anum的子集但是 如果i为Anum,a[i]对应的值就不是Bnum。

即如 A[7] 对应的Anum=35 不是Bnum 所以 B[7] 为37(第7个Bnum数)


思路: 先二分一个数mid ,找到<=mid 的mid最小的区间满足等于n个Bnum的数 ,此数就是

,如何求区间内Bnum 的数量呢,,先求Anum的数量,然后对Anum的数量(即为下标)这个区间再求Anum 的数量,

两者相减就是Bnum的数量了 、。

即:


此题需要注意的地方,1.会超 long long 所以要用unsigned longlong  否则会TLE, 2 定义的类型容易全部都写成int类型。 3 二分的写法。


参考网址:http://blog.csdn.net/became_a_wolf/article/details/51503211

#include <cstdio>         ///此代码书写的时候出现了LL 定义成了int的情况
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<cmath>
#include<cstdlib>
#define LL unsigned long long
#define bug puts("***********")
#define INF 0x3f3f3f3f

using namespace std;

const LL R=((LL)1<<63)-1;
int num=0;
int bit[100];
LL dp[100][100][2];
LL DFS(int p,int m,int have,int flag)
{
    if(p==0) return have||(m==0);
    if(flag&&dp[p][m][have]!=-1) return dp[p][m][have];
    int k=flag?9:bit[p];
    LL sum=0;
    for(int i=0; i<=k; i++)
    {
        sum=sum+DFS(p-1,(m*10+i)%7,i==7||have,flag||i!=k);
    }
    if(flag) dp[p][m][have]=sum;
    return sum;
}
LL solve(LL x)
{

    if(x<0)
        return 0;
    num=0;
    while(x)
    {
        bit[++num]=x%10;
        x/=10;
    }
    return DFS(num,0,0,0)-1;
}
LL get(LL m)
{
    LL ans1=solve(m);              /// LL
    return ans1-solve(ans1);  ///Anum - num(下标是Anum的数的 个数)
}
int main()
{
    LL n;
    while(~scanf("%lld",&n))
    {
        memset(dp,-1,sizeof(dp));
        /// cout<<get(n)<<endl;
        LL high =R;
        //cout<<pow(2,63)-1;
        LL low=0;
        LL ans=0;
        LL num1,num2,num3;
        while(low<=high)
        {
            LL mid=(high+low)>>1;

            num2=get(mid);


            /// 这种二分的方法不对,因为并不是唯一的一个mid对应一个num2,
            ///而是存在多个mid对应一个num2 的情况,并且应该取符合条件的最小mid才行
//            if(num2==n)    .
//            {
//                ans=mid;
//                break;
//            }
//            else if(num2>n)
//                high=mid-1;
//            else
//                low=mid+1;

            if(num2<n)
                low=mid+1;
            else
            {
                ans=mid;
                high=mid-1;
            }
        }
        printf("%lld
",ans);
    }
    return 0;
}
    






原文地址:https://www.cnblogs.com/zswbky/p/6717891.html