Problem
d (
d≥2
)进制下的 好数
被定义为:K=
(A1A2...Ad)d
, 其中
Ai≠Aj
且
0≤Ai≤d−1
,同时
A1≠0
求区间 [L,R] 中多少数恰好是 d 进制下的好数 ?
Limit
1≤L≤R≤105000
L,R 为十进制数
Idea
d 进制的最大好数值为 (d−1)⋅dd−1+(d−2)⋅dd−2+⋯+0⋅d0
d 进制的最小好数值为 1⋅dd−1+0⋅dd−2+2⋅dd−3+⋯+(d−1)⋅d0
故 d-1 进制下的最大好数与 d 进制下的最小好数满足:
(d−2)⋅(d−1)d−2+(d−3)⋅(d−1)d−3+⋯+0⋅(d−1)0<1⋅dd−1+0⋅dd−2+2⋅dd−3+⋯+(d−1)⋅d0
即 d 进制的好数区间与 d-1 进制的好数区间不会重叠。
故具体做法为:
STEP 1. 分别处理子问题 [1, L] 和 [1, R] 中有多少满足条件的好数
STEP 2. 二分枚举最大的 d 进制满足 d 的右区间仍 ≤N (N 作为区间的右值 [1, N]) 。
STEP 3. 在二分后,处理 d+1 进制中有多少满足条件的好数,额外添加即可。
STEP 4. 在处理 d+1 进制时,优先将 N 转换为 d+1 进制的数组,dfs 处理最大的 d+1 进制数。利用康托展开计算个数。
Code
import java.io.*;
import java.math.BigInteger;
import java.util.Scanner;
public class HDU_6093 {
public static void main(String[] args) throws IOException {
SolverHDU_6093 solver = new SolverHDU_6093();
solver.run();
}
}
class SolverHDU_6093 {
private static final int MOD = 998244353;
private static final int N = 5000;
private int t;
private BigInteger L, R;
private long[] factorial = new long[N];
private int[] N2d = new int[N];
private boolean[] vis = new boolean[N];
private int[] maxDNum = new int[N];
private int[] bit = new int[N];
int lowbit(int x) {
return x & -x;
}
void add(int x) {
for (int i=x;i<N;i+=lowbit(i)) {
bit[i]++;
}
}
int get(int x) {
int res = 0;
for (int i=x;i!=0;i-=lowbit(i)) {
res += bit[i];
}
return res;
}
void init() {
factorial[0] = 1;
for (int i=1;i<N;i++) {
factorial[i] = factorial[i-1] * i % MOD;
}
}
boolean dfs(int idx, int d) {
if (idx > d) return true;
if (vis[ N2d[idx] ] == true) {
for (int p=N2d[idx];p>=0;p--) {
if (vis[p] == false) {
vis[p] = true;
maxDNum[idx] = p;
break;
} else if (p == 0) {
return false;
}
}
for (int i=idx+1, p=d-1;i<=d;i++) {
while (vis[p]) p--;
maxDNum[i] = p;
vis[p] = true;
}
return true;
} else {
maxDNum[idx] = N2d[idx];
vis[ N2d[idx] ] = true;
if (dfs(idx+1, d) == false) {
vis[ N2d[idx] ] = false;
if (N2d[idx] == 0) return false;
for (int p=N2d[idx]-1;p>=0;p--) {
if (vis[p] == false) {
vis[p] = true;
maxDNum[idx] = p;
break;
} else if (p == 0) {
return false;
}
}
for (int i=idx+1, p=d-1;i<=d;i++) {
while (vis[p]) p--;
maxDNum[i] = p;
vis[p] = true;
}
return true;
} else {
return true;
}
}
}
long calcExt(BigInteger N, int d) {
BigInteger NN = N;
for (int i=d;i!=0;i--) {
N2d[i] = (int) NN.mod(BigInteger.valueOf(d)).longValue();
NN = NN.divide(BigInteger.valueOf(d));
}
if (N2d[1] == 0) return 0;
for (int i=0;i<=d;i++) vis[i] = false;
vis[ N2d[1] ] = true;
maxDNum[1] = N2d[1];
if (dfs(2, d) == false) {
if (N2d[1] - 1 == 0) return 0;
vis[ N2d[1] ] = false;
vis[ N2d[1] - 1 ] = true;
maxDNum[1] = N2d[1] - 1;
int p = d-1;
for (int i=2;i<=d;i++) {
while (vis[p] == true) p--;
maxDNum[i] = p;
vis[p] = true;
}
}
long ans = 0, dig;
for (int i=1;i<=d;i++) bit[i] = 0;
for (int i=d;i!=0;i--) {
dig = get(maxDNum[i]);
add(maxDNum[i] + 1);
if (i == 1) dig--;
ans = (ans + dig * factorial[d-i]) % MOD;
}
return (ans+1) % MOD;
}
long calc(BigInteger N) {
int l = 2, r = 2000, mid, d = 1;
BigInteger maxNumInD;
while (l <= r) {
mid = (l+r) / 2;
maxNumInD = BigInteger.ZERO;
for (int j=mid-1;j>=0;j--) {
maxNumInD = maxNumInD.multiply(BigInteger.valueOf(mid));
maxNumInD = maxNumInD.add(BigInteger.valueOf(j));
}
if (maxNumInD.compareTo(N) <= 0) {
l = mid + 1;
d = mid;
} else {
r = mid - 1;
}
}
if (d == 1) return 0;
long ans = 0;
for (int i=2;i<=d;i++) {
ans += factorial[i] - factorial[i-1];
ans %= MOD;
}
ans += calcExt(N, d+1);
ans %= MOD;
return ans;
}
void run() throws IOException {
init();
Scanner cin = new Scanner(System.in);
t = cin.nextInt();
for (int ica=1;ica<=t;ica++) {
L = cin.nextBigInteger();
R = cin.nextBigInteger();
long left = calc(L.subtract(BigInteger.ONE));
long right = calc(R);
System.out.println((right - left + MOD) % MOD);
}
}
}