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\n", solve(n));
}
}
题目大意:
A数组就是民间游戏”敲七”的序列
B数组就是
{x∣∣xi∈{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在统计一下相减即可;
即:
- 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\n",ans);
}
int main(){
// printf("%lld\n",~(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\n",ans);
}
return 0;
}