A-Number and B-Number
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.
Now Given an integer N, please output the Nth B-number
Input
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
积累点:
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数组就是
然后输出B数组中第n个元素即:
解题思路:
如果直接求第i个元素的话 无论是A数组还是B数组都不能求(反正我不会)
但是他求得是第
首先我们可以用一个入门级别的数位dp来统计小于
那么反过来 [1,n]中 最小的有x个元素的n 就是第x个元素
那么我们就可以用二分答案来统计了
对于A数组我们很好数位dp
但是对于B数组我们不能直接求出来
但是考虑A与B数组的关系,发现,只要对cal(x)统计的结果ans在统计一下相减即可;
即:
- 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 二分的写法。
#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; }