Rikka with Number
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 409 Accepted Submission(s): 126
In radix d , a number K=(A1A2...Am)d(Ai∈[0,d),A1≠0) is good if and only A1−Am is a permutation of numbers from 0 to d−1 .
A number K is good if and only if there exists at least one d≥2 and K is good under radix d .
Now, Yuta wants to calculate the number of good numbers in interval [L,R]
It is too difficult for Rikka. Can you help her?
For each testcase, the first line contains two decimal numbers L,R(1≤L≤R≤105000) .
2 5 20 123456 123456789
3 114480
一个数K,如果表示成d进制(d>=2)并且表示成d进制后是由(0~d-1)的排列组成的(前导不能是0)。那么K就是一个good number。
只要存在任意一个d进制,能使数K满足上面说的条件,那么数k就是D进制。
然后给出T组测试数据,让求L到R区间里面有多少个goodnum。
思路来源:http://www.zhimengzhe.com/bianchengjiaocheng/cbiancheng/356607.html
他是用java的大数类写的,我看了他的思路后,他的有些代码我看不懂,所以在看不懂的地方我就按我的想法写了。
第一次用java交题,错了好多次,从eclipse里面粘出来了后,一直带着package 包名,提交,提交了十几次都Wa了。到后来才意识到自己带着package这句话提交了。所以用java写的时候,提交要小心。
现在就来说解题思路吧:
首先说明两点:
假如当前我们要找的是是d进制组成的good number。首先我们知道good number用d进制表示,必须有d位。
而且是0~d-1的一个散列,所以d进制构成的good number总共有:
第一位不能是0,所以第一位可以选择:d-1 ,d-2,........,3,2,1。总共有d-1种选择。
选了第一位后,第二位带上有d-1种选择。
选了第二位后,除掉两个数字,第三位有d-2种选择。
选了第三位后,除掉三个已经选择的数字,第三位有d-3种选择。
因此d进制构成的good number总共有(d-1)*fac(d-1) PS:fac(x) 代表x的阶乘。
在这么多符合条件的数字种。
10234.....d-3,d-2,d-1 是d进制构成的good number中最小的那个数。记作:Min_d;
d-1,d-2,d-3,.....3,2,1,0 是d进制构成的good number中最大的那个数。仅作:Max_d;
例如6进制中的最小good numbers是:六进制(102345),最大的good number是:六进制 (543210)
因此我们可以打表,先算出每种进制构成的good number中最大的那个数。
接着对于题目给定的区间(L,R)。
我们可以通过二分查询,查找第一个good number种的最大数大于L的进制,记为:left
同样查找第一个good number种最大数大于R的进制,记作:right.
可以看下图,情况比较多。
我们可以看到图中有三种情况,第一个图,先找到了相应的left进制和right进制,由于left进制下的goodnum最
大值和最小值,可能横跨L,那么left进制中共有(left-1)*fac(left-1)这个多个goodnum就不全都位于所求区间。
同理right进制的数,也可能横跨R,那么right进制中共有(right-1)*fac(right-1)这么多个goonum就不全都位于
所求的区间。因为我们先不来考虑横不横跨边界的情况,则我们可以考虑left+1 进制~right-1进制下的goodnum
一定是位于L,R这个区间的。前提条件是(left+1)<=right-1。因为如果left+1>right-1。这样是不合理的。
所以我们先计算left+1进制到right-1进制下goodnum的个数。
ans = 0;
for(int i = left+1; i <= right-1; i++)
ans += (i-1)*fac(i-1);
然后呢我们就要求L~Max_left(Max_left是left进制下最大的那个goodnum)中有多少个goodnum。
求解过程是这样的,我们需要在left进制下求L的每一位:
最高位的权值是weight = left^(left-1)。所以如果L/weight向下取整答案是0的话。则说明Min_left是在L的
右侧,如第二个图哪种情况所示,因此,left进制中的所有goodnum也都是答案的一部分,那么求出
这部分,就可以结束了。如果当前最高位不是0。求解过程如下所示:
举个例子:如果L化成6进制是 524301的话。则425301 ~ 543210中的答案数量是这样计算的。
L 除以 6^5后,得到最高位是4...,并标记4,是用过的。
大于4且没有标记的数为5,则5开头的全排列都是满足条件的。
L= L%6^5
L除以6^4后,得到次高位:2。 对于前面固定的一位数字4,
次高位可以选择没有用过的数字3,5,然后根剩余的数全排列。
每次都这样计算。。。。。直到处理到最低位:
同理可以用这个方法,求除R~Max_right中的right进制goodnum数量。这样的话可以用right进制下
所有的goodnum数量减去这个数字,就得到Min_right~R中的right进制goodnum数量。
还有一种情况就是L~R这个区间比较短,那么left和right会是同一个数,这时候的答案
就应该直接等于L~Max_left的goodnum数量-R~Max_right的goodnum数量。
易错点:
代码中会包含先相减后取模,由于参与运算的数已经是取过模的数,所以相减的时候有可能会得到负数,
此时应该先加上mod后再对mod取模。
具体详情请见代码:
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
static int maxn = 1600;
static BigInteger[] base = new BigInteger[maxn]; ///base[i]进制下的最大goodnum数组
static int mod = 998244353;
static long[] fac = new long [maxn]; ///存放数的阶乘
static void Init() {
base[0] = BigInteger.ZERO;
base[1] = BigInteger.ZERO;
/*i代表进制,i进制,0~i-1排列出的最大数字是i-1,i-2,i-3,....3,2,1,0,计算
每个进制中的这个最大数。*/
for(int i = 2; i < maxn; i++) {
base[i] = BigInteger.ZERO;
for(int j = i-1; j >= 0; j--) {
base[i] = base[i].multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(j));
}
}
fac[0] = fac[1] = 1; ///0的阶乘和1的阶乘的值为1.
/*i进制由0~i-1组成的排列数为i的阶乘种*/
for(int i = 2; i < maxn; i++){
fac[i] = fac[i-1]*i%mod;
}
}
/*找到第一个最大排列值大于num的i进制排列*/
static int binarySearch(BigInteger num) {
int low = 0,high = maxn-1,mid;
while(low < high) {
mid = (low+high)/2;
if(base[mid].compareTo(num)>=0) high = mid;
else low = mid+1;
}
return high;
}
public static void main(String[] args) {
Init();
Scanner input = new Scanner(System.in); ///java中的输入
int t = input.nextInt();
int[] vis = new int[maxn];
while(t!=0) {
t--;
BigInteger L,R;
L = input.nextBigInteger();
R = input.nextBigInteger();
int leftbase,rightbase;
leftbase = binarySearch(L);
rightbase = binarySearch(R);
leftbase++;
rightbase--;
long ans;
if(leftbase>rightbase) {
ans = 0;
}else {
ans = 0;
for(int i = leftbase; i <= rightbase; i++)
ans = (ans + (i-1)*fac[i-1]%mod)%mod;
}
leftbase--;
rightbase++;
///求L到leftbase符合要求的数
BigInteger weight = (BigInteger.valueOf(leftbase)).pow(leftbase-1);
for(int i = 0; i < maxn; i++) {
vis[i] = 0;
}
long ans1 = 0;
for(int i = leftbase-1; i >= 0; i--) {
int top = L.divide(weight).intValue();
if(top == 0 && i == leftbase-1) {
ans1 = (ans1 + (leftbase-1)*fac[leftbase-1]%mod)%mod;
break;
}
int cnt = 0;
for(int j = top+1; j < leftbase; j++) {
if(vis[j]==0)
cnt++;
}
ans1 = (ans1 + cnt*fac[i]%mod)%mod;
if(vis[top]==0 && i == 0) ans1 = (ans1+1)%mod;
if(vis[top]==1) break;
L = L.mod(weight);
vis[top] = 1;
weight = weight.divide(BigInteger.valueOf(leftbase));
}
long ans2 = 0;
for(int i = 0; i < maxn; i++) {
vis[i] = 0;
}
weight = (BigInteger.valueOf(rightbase)).pow(rightbase-1);
for(int i = rightbase-1; i >= 0; i--)
{
int top = R.divide(weight).intValue();
if(top == 0 && i == rightbase-1) {
ans2 = (ans2 + (rightbase-1)*fac[rightbase-1]%mod)%mod;
break;
}
int cnt = 0; ///统计没标记过的且大于top的数字个数。
for(int j = top+1; j < rightbase; j++) {
if(vis[j]==0)
cnt++;
}
ans2 = (ans2 + cnt*fac[i]%mod)%mod;
if(vis[top]==1) break;
R = R.mod(weight);
vis[top] = 1;
weight = weight.divide(BigInteger.valueOf(rightbase));
}
if(leftbase == rightbase) {
ans = ((ans1-ans2)%mod+mod)%mod;
}
else
{
long tmp = (((rightbase-1)*fac[rightbase-1]-ans2)%mod+mod)%mod;
ans = (ans + ans1 + tmp)%mod;
}
System.out.println(ans);
}
input.close();
}
}