积累点:
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那个位置。
代码:
#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)); } }