HDU 6093 Rikka with Number(康托展开)

256 篇文章 0 订阅

Description

定义一个数 K 是好数当且仅当存在d2使得 K d进制下有 d 位且这d位的值是一个 0 ~d1的排列,给出一个区间 [L,R] ,问该区间中有多少个好数,结果模 998244353

Input

第一行一个整数 T 表示用例组数,每组用例输入两个十进制数L,R (1T10,1LR105000)

Output

对于每组用例,输出区间 [L,R] 中好数的数量,结果模 998244353

Sample Input

2
5 20
123456 123456789

Sample Output

3
114480

Solution

求出 [1,n] 好数即可, d 进制下的好数,其最小值为(1,0,2,3,...,d1)d>dd1,其最大值为 (d1,d2,...,0)d <dd ,由 (d+1)d>dd 知相邻两个进制的好数不交,即不存在在两个不同的进制下都是好数的数, d 进制下好数有d!(d1)!(即总排列数减去第一位是 0 的排列数),所以只需要考虑包含n的临界值即可,用对数估计得到 d 进制好数转化为十进制后的大致长度,对估计出的临界值的附近几个值(亲测两个值)去求解,把n转化为 d 进制后,类似康托展开的计数即可得到不大于n d <script type="math/tex" id="MathJax-Element-131">d</script>进制好数

Code

#include<cstdio>
#include<cmath>
#include<cstring> 
using namespace std;
typedef long long ll;
#define maxn 5005
#define mod 998244353
int L[maxn],fact[maxn];
void init(int n=5000)
{
    int len=1;
    while(L[len]<=n)
    {
        len++;
        L[len]=log10(len)*(len-1);
    }
    fact[0]=1;
    for(int i=1;i<=n;i++)fact[i]=(ll)i*fact[i-1]%mod;
}
int dec(int x,int y)
{
    return x-y<0?x-y+mod:x-y;
}
int inc(int x,int y)
{
    return x+y>=mod?x+y-mod:x+y;
}
int T,a[maxn],b[maxn],c[maxn],mark[maxn];
char s[maxn];
int Deal(int *a,int len,int m)//把a转化成m进制 
{
    int n=0;
    for(int i=1;i<=len;i++)c[i]=a[i];
    while(len)
    {
        if(n>m)return n;
        int pre=0;
        for(int i=len;i;i--)
        {
            int temp=pre*10+c[i];
            pre=temp%m,c[i]=temp/m;
        }
        b[++n]=pre;
        while(len&&c[len]==0)len--;
    }
    return n;
}
int Count(int *a,int len,int m)
{
    int n=Deal(a,len,m);
    if(n<m)return 0;
    if(n>m)return dec(fact[m],fact[m-1]);
    for(int i=0;i<n;i++)mark[i]=0; 
    int ans=(ll)(b[n]-1)*fact[n-1]%mod;
    mark[b[n]]=1;
    for(int i=n-1;i;i--)
    {
        for(int j=0;j<b[i];j++)
            if(!mark[j])ans=inc(ans,fact[i-1]);
        if(mark[b[i]])break;
        mark[b[i]]=1;
        if(i==1)ans=inc(ans,1);
    }
    return ans;
}
int Solve(int *a,int len)
{
    if(len==0)return 0;
    int pos=2;
    while(L[pos]<len)pos++;
    int ans=dec(fact[pos-3],1);
    for(int i=pos-2;i<pos;i++)ans=inc(ans,Count(a,len,i));
    return ans;
}
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1);
        int len=strlen(s+1);
        for(int i=1;i<=len;i++)a[i]=s[len+1-i]-'0';
        a[1]--;
        for(int i=1;i<=len;i++)
            if(a[i]<0)
                a[i+1]--,a[i]+=10;
        while(len&&a[len]==0)len--;
        int ans=Solve(a,len);
        scanf("%s",s+1);
        len=strlen(s+1);
        for(int i=1;i<=len;i++)a[i]=s[len+1-i]-'0';
        ans=dec(Solve(a,len),ans);
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值