HDU 6093 Rikka with Number (2017 Multi-University Training Contest - Team 5)

HDU 6093 Rikka with Number

题目描述:
As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some math tasks to practice. There is one of them:

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?  

Input

The first line contains a number t(1≤t≤20), the number of the testcases. 

For each testcase, the first line contains two decimal numbers L,R(1≤L≤R≤10^5000).

Output

For each testcase, print a single line with a single number -- the answer modulo 998244353.
链接:
http://acm.hdu.edu.cn/showproblem.php?pid=6093    (2017 Multi-University Training Contest - Team 5)
题目大意:
模拟题,给一个L到R的闭区间,求区间内的good number的数量(1≤L≤R≤1e5000)。
对于good number的定义:
    将一个数字num转换为k进制,恰好为0 - k-1的k个数字的一种排列组合(不包括前导0),则num为一个good number。
分析:
  1. 对于x位d进制数转换成10进制数字num后,num的位数y满足条件:d^x=10^y,得y = (x-1) * log(d) / log(10)。
  2. 由于本题的数据范围很大,需要进行高精度的进制转换。
  3. d进制的good number的数量为d!-(d-1)!。其十进制数对应的范围为(d^(d-1),d^d)。
  4. 对于问题,可以求解[0,L-1]区间的good number,与[0,R]区间的good number,它们的差即为answer。
  5. 根据4.结论,本问题转换为求解[0,i]区间内的good number的个数,根据1.与3.可以判断出该区间内的good number来源的进制d的范围,枚举每个d即可得到答案。
  6. 对于过程5.可以优化,当d>15时,[2,d-2]区间的进制对应的good number一定包含在[0,L-1]区间内,只需计算d-1、d进制所产生的good number是否在本区间内即可。
  7. 对于6.中的计算,可以采用类似康托展开定理的方式进行枚举。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5000+50;
const int mod=998244353;

vector <double> base;int len_base;//每个进制下最小good number,转换成10进制的位数、长度
int jc[maxn];//阶乘
char l[maxn],r[maxn];//左区间L、右区间R
int rl[maxn],rr[maxn],len_rl,len_rr;//L-1、R的倒序存储数组及其长度

void base_init();//得到base数组
void get_jc();//得到阶乘数组
void get_rs(char *s,int num,int *rs,int &l);//将s数组对应的数字 -num,倒序存储在rs数组中,rs数组长度为l
int get_base(int len);//len长度的10进制数组能包含的good number的最大进制。
int calc(int *s,int len,int d);//在d进制下找到小于s的good number的个数。
int get_ans(char *s,int num,int *rs,int &l);//找到[1,s-num]区间的good number的个数
int main()
{
    base_init();//得到base数组
    get_jc();//得到阶乘数组
    int t;scanf("%d",&t);
    while(t--){//t组数据
        scanf("%s",l);
        int ans = get_ans(l,1,rl,len_rl);//得到[1,L-1]区间的答案
        scanf("%s",r);
        ans = (get_ans(r,0,rr,len_rr)-ans+mod)%mod;
        printf("%d\n",ans);
    }
    return 0;
}

void base_init(){
    base.push_back(0);base.push_back(0);
    for(int i=2;;i++){
        base.push_back((i-1)*log(i)/log(10));
        len_base = i;
        if((i-1)*log(i)/log(10)>5000)break;
    }
}
void get_jc(){
    jc[0]=1;
    for(int i=1;i<=len_base;i++) jc[i] = (long long)jc[i-1]*i%mod;
}
int get_ans(char *s,int num,int *rs,int &l){
    get_rs(s,num,rs,l);//高精度的处理,将s-num转换为int数组倒序存储,l为长度。
    int d=get_base(l);//找到[1,s-sum]区间能得到的最大的good number所对应的进制d。
    int ans = 0;
    if(d>15){//一个优化
        for(int i=2;i<=d-2;i++)ans=((long long)ans+jc[i]-jc[i-1]+mod)%mod;
        ans =((long long)ans+calc(rs,l,d-1))%mod;
        ans =((long long)ans+calc(rs,l,d))%mod;
    }
    else
        for(int i=2;i<=d;i++) ans =((long long)ans+calc(rs,l,i))%mod;//rs在i进制下的good number的数量。
    return ans;
}
void get_rs(char *s,int num,int *rs,int &l){
    int len = strlen(s);
    for(int i=0;i<len;i++)
        rs[i]=s[len-1-i]-'0';
    rs[0]-=num;
    for(int i=0;i<len;i++) while(rs[i]<0)rs[i+1]--,rs[i]+=10;
    l=0;
    for(int i=len-1;i>=0;i--)
        if(rs[i]!=0){l=i+1;break;}
}
int get_base(int len){
    for(int i=1;i<=len_base;i++)
        if(base[i]>len) return i-1;
}
int calc(int *s,int len,int d){
    int temp[maxn],changes[maxn],len_changes=0;//changes存放s转换为d精度的数值。
    memcpy(temp,s,sizeof(int)*len);
    while(len){
        int x=0;
        for (int i=len-1;i>=0;i--){
            int pre=x; x=(x*10+temp[i])%d; temp[i]=(pre*10+temp[i])/d;
        }
        changes[len_changes++]=x; while (len&&temp[len-1]==0) len--;
    }
    if(len_changes>d) return (jc[d]-jc[d-1]+mod)%mod;
    if(len_changes<d) return 0;
    int point[maxn];
    for(int i=0;i<d;i++) point[i]=0;
    int res = 0;
    for(int i=1;i<changes[len_changes-1];i++) res=((long long)res+jc[d-1])%mod;
    point[changes[len_changes-1]]=1;
    for(int i=len_changes-2;i>=0;i--){
        for(int j=0;j<changes[i];j++) if (point[j]==0) res =((long long)res + jc[i])%mod;
        if(point[changes[i]])break;
        point[changes[i]]=1;
        if(i==0) res =(res+1)%mod;
    }
    return res;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值