SDOI 2010 代码拍卖会 题解

博客围绕一道题目展开,题目是求由1 - 9组成、位数为n且不递减的数中能被p整除的个数。题解采用数位dp方法,从p入手,利用1 - 9个类似1,11,111...的数相加组合满足要求的数,找到循环节统计sum[i]进行dp,还对组合数计算进行了优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

题目大意: 问位数为 n n n 的数(由 1 1 1 ~ 9 9 9 组成,并且要求不递减)中,有多少个数可以被 p p p 整除。

题解

这题 n n n大成这个鬼样,显然是要用数位 d p dp dp 了,但是,很要命的是,光是把每一位过一次就已经毫无疑问地TLE了,所以,只能从 p p p入手了。

这样看来,这道题里,数的组成不像一般的题一样,枚举每一位然后将每一位组合起来,于是,需要考虑其它方法去组合满足要求的数。

因为这个数的每一位是不递减的,所以,这个数其实可以由 1 1 1 ~ 9 9 9 个如同 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,... 这样的数相加得到,但是,因为需要保证这个数的位数等于 n n n,所以我们必定要选取 “ 1..1 ( n 1..1(n 1..1(n 1 ) 1) 1)” 这个数。

然后又神奇地发现, 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,...它们 m o d   p mod~p mod p的值是循环的1,如同一个无限循环小数,这里定义一个数组, s u m [ i ] sum[i] sum[i] 表示 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,...这些数里面有多少个数 m o d   p = = i mod~p==i mod p==i,那么,只需要找到“循环节”,就可以快速统计出 s u m [ i ] sum[i] sum[i],于是再利用 s u m [ i ] sum[i] sum[i] 进行 d p dp dp 即可。

f [ i ] [ j ] [ l ] f[i][j][l] f[i][j][l] 表示处理完 s u m [ 0 sum[0 sum[0 ~ i − 1 ] i-1] i1],当前组成的数 m o d   p mod~p mod p 的值为 j j j,用了 l l l 个数进行组合。每次再枚举一个 u u u,表示选取 u u u m o d   p = = i mod~p==i mod p==i 的数,于是得到方程:
f [ i + 1 ] [ ( j + u × i )   m o d   p ] [ l + u ] + = f [ i ] [ j ] [ l ] × C s u m [ i ] + u − 1 u f[i+1][(j+u\times i)\bmod p][l+u]+=f[i][j][l]\times C_{sum[i]+u-1}^u f[i+1][(j+u×i)modp][l+u]+=f[i][j][l]×Csum[i]+u1u

经过实际测试后,发现这份代码 80 % 80\% 80% 的时间都是这个 C C C 贡献的(具体参见我可怜的TLE提交记录,虽然吸口氧还是可以过的),于是接下来对它进行一下优化。

仔细观察,发现 C s u m [ i ] + u − 1 u C_{sum[i]+u-1}^u Csum[i]+u1u 只有 p × u p\times u p×u 种情况,也就是说,我只需要存下 5000 5000 5000 个组合数的值即可,但是 s u m [ i ] + u − 1 sum[i]+u-1 sum[i]+u1 十分的大,如果用 m a p map map 存的话,时间复杂度依然突破天际,于是只能碰碰运气把它 h a s h hash hash一下了。

而且再观察一下,发现 C s u m [ i ] + u − 1 u C_{sum[i]+u-1}^u Csum[i]+u1u u u u 是十分小的,于是对于 C i j C_i^j Cij,可以把它弄成这种形式:
C ( i , j ) = i ! j ! ( i − j ) ! = ( i − j + 1 ) ∗ ( i − j + 2 ) ∗ . . . ∗ i j ! C(i,j)=\frac {i!} {j!(i-j)!}=\frac {(i-j+1)*(i-j+2)*...*i} {j!} C(i,j)=j!(ij)!i!=j!(ij+1)(ij+2)...i

显然后面那个式子的时间复杂度只有 O ( j ) O(j) O(j),在这题中,也就是 O ( 9 ) O(9) O(9),十分可观。

代码如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
using namespace std;
#define ll long long
#define mod 999911659ll
#define hash 973327

ll sum[550],k[550];
ll n,f[550][550][15];
int p;
ll c[1000010][10];
void work()//预处理sum数组
{
    int tt=0,tot=0;
    while(k[(tot*10+1)%p]==0&&tt<n)//寻找循环节
    {
    	tot=(tot*10+1)%p;
    	k[tot]=++tt;
    }
    if(tt==n)//注意要特判一下还没找到循环节就到头了的情况
    {
        for(int i=0;i<p;i++)
        sum[i]=k[i]!=0;
        f[0][tot][1]++;//别忘了f数组的预处理
        return;
    }
    int l=tt-k[(tot*10+1)%p]+1;
    int kk=tot;
    for(int i=1;i<=l;i++)//处理中间的循环节
    {
        kk=(kk*10+1)%p;
        sum[kk]=(n-k[(tot*10+1)%p]+1)/l;
    }
    kk=0;//处理循环节前面的部分
    for(int i=1;i<=k[(tot*10+1)%p]-1;i++)
    sum[kk=(kk*10+1)%p]++;
    kk=tot;//处理循环节后面的部分
    for(int i=1;i<=(n-k[(tot*10+1)%p]+1)%l;i++)
    sum[kk=(kk*10+1)%p]++;
    f[0][kk][1]++;
}
ll ksm(ll x,ll y)
{
    ll ans=1,tot=x;
    while(y>0)
    {
        if(y%2==1)ans=ans*tot%mod;
        y/=2;tot=tot*tot%mod;
    }
    return ans;
}
ll C(ll x,ll y)
{
    if(x<y)return 1ll;
    ll ans=1;
    for(ll i=x-y+1;i<=x;i++)
    ans=ans*(i%mod)%mod;
    ll yy=1;
    for(ll i=2;i<=y;i++)
    yy=yy*i%mod;
    return ans*ksm(yy,mod-2)%mod;//利用逆元做除法
}

int main()
{
    scanf("%lld %d",&n,&p);
    work();
    for(int i=0;i<p;i++)
    for(int j=0;j<=8;j++)
    c[(sum[i]+j-1)%hash][j]=C(sum[i]+j-1,j);
    for(int i=0;i<p;i++)
    {
    	if(sum[i]==0)//假如没有mod p==i的数,就直接继承上一层的f数组
        {
            for(int l=1;l<=9;l++)
            for(int j=0;j<p;j++)
            f[i+1][j][l]=f[i][j][l];
            continue;
        }
        for(int l=1;l<=9;l++)
        for(int j=0;j<p;j++)
        for(int u=0;u<=9-l;u++)
        {
            f[i+1][(j+u*i)%p][l+u]+=f[i][j][l]*c[(sum[i]+u-1)%hash][u];
            f[i+1][(j+u*i)%p][l+u]%=mod;
        }
    }
    ll ans=0;
    for(int i=1;i<=9;i++)
    ans=(ans+f[p][0][i])%mod;
    printf("%lld",ans);
}

  1. 关于这个循环节存在性的证明——
    因为 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,... 它们 m o d   p mod~p mod p的值一定是 0 0 0 ~ p − 1 p-1 p1 中的一个,所以,在 p p p 个数之内,一定会出现 m o d   p mod~p mod p 的值相同的情况,那么此时,就出现了一个特殊的东西。
    假设 x x x 1   m o d   p 1~mod~p 1 mod p的值 与 y y y 1   m o d   p 1~mod~p 1 mod p 的值 是相等的 ( x < y ) (x<y) (x<y),那么就说明存在一个数 11..100..0 11..100..0 11..100..0 y − x y-x yx 1 1 1 x x x 0 0 0),这个数 m o d   p mod~p mod p 的值一定是 0 0 0。那么也就是说,对于后面由比 y y y 个更多的 1 1 1 组成的数,其实他们前面的若干位 m o d   p mod~p mod p 都是 0 0 0↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值