[BZOJ1974][SDOI2010]代码拍卖会(规律+DP)

本文探讨了一种处理大数问题的有效DP方法,通过将【上升数】进行拆分并利用循环节特性,设计了一个三维DP方程,解决了选择某些数使其模p等于0的问题。文中详细解释了DP状态定义、转移方程,并提供了完整的C++实现代码。

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

题目:

我是超链接

题解:

首先这个n特别大,而且这个转移怎么也是二维的,所以矩乘也不像。

我们考虑拆一下这样一个【上升数】,11234=11111+111+11+1,每一个【上升数】被拆完之后不会超过9位,而且为了避免总共选0位有前导零,我们起初先填上11…111(共n位)。

这样会发现一个规律:1,11,111,1111…这些数字%p会有一个循环节(循环节不一定是从第一位开始的!)。那么我们设cnt[i]表示n位数字%p=i的数的个数,事实上要选择一些数使%p=0也就是选择cnt的下标使得%p=0的选法。

DP方程就可以写了:f[i][j][k]表示目前选到cnt[i],%p=j,选择了k个的方案数,其中i,j<=p,k<=9。这样的复杂度就是O(p^2*9*9),可以通过此题

f[i][(j+li)%p][k+l]=f[i1][j][k]C(cnt[i]+l1,l) f [ i ] [ ( j + l ∗ i ) % p ] [ k + l ] = ∑ f [ i − 1 ] [ j ] [ k ] ∗ C ( c n t [ i ] + l − 1 , l )

其中在n个数里选m个,每个元素可以重复选的方案数是C(n+m-1,m),可以预处理

要注意,为了避免总共选0位有前导零,我们起初先填上11…111(共n位),这样DP方程k可以选择的范围是[0,8],而且最后的答案也不一定是f[last][0][j],而是除去了这n位1%p的值后,使总的%p=0的值

其实数组f[500][500][9]也可以开下,但是我们可以用滚动数组来优化。

代码:

#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;
const int mod=999911659;
int f[2][505][10],hh[505],bj[505];
LL cnt[505],c[505][20],inv[20];
LL C(LL n,LL m)
{
    LL ans=inv[m];
    for (n%=mod;m--;n--) ans=ans*n%mod;
    return ans;
}
int main()
{
    int xhj=0,p,id=0,add=0;LL n;
    scanf("%lld%d",&n,&p);
    if (n<=p)
    {
        while (id<n)
        {
            id++;add=(add*10+1)%p;
            cnt[add]++;
        }
    }
    else
    {
        int len=0;
        while (1)
        {
            add=(add*10+1)%p;
            if (bj[add]) break;
            cnt[add]++,bj[add]=++len,hh[len]=add;
        }
        xhj=len;len=len-bj[add]+1;
        LL z=(n-xhj)/len,y=(n-xhj)%len;
        for (int i=bj[add];i<bj[add]+len;i++) cnt[hh[i]]+=z;
        for (int i=1;i<y;i++) 
          cnt[add]++,add=(add*10+1)%p;
        if (y>=1) cnt[add]++; 
    }
    add=(p-add)%p;
    inv[0]=inv[1]=1;
    for (int i=2;i<=9;i++) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=2;i<=9;i++) inv[i]=1LL*inv[i]*inv[i-1]%mod;
    for (int i=0;i<p;i++) if (cnt[i])
      for (int j=0;j<9;j++) c[i][j]=C(cnt[i]+j-1,j);
    f[0][0][0]=1;
    for (int i=1;i<9;i++) f[0][0][i]=c[0][i];
    int now=0;
    for (int i=1;i<p;i++)
     if (cnt[i])
    {
        now^=1; memset(f[now],0,sizeof(f[now]));
        for (int j=0;j<p;j++)
          for (int k=0;k<9;k++) //当前 
            if (f[now^1][j][k])
            for (int l=0;l<9-k;l++) //要加
               f[now][(j+l*i)%p][k+l]=(f[now][(j+l*i)%p][k+l]+f[now^1][j][k]*c[i][l]%mod)%mod;
    }
    int ans=0;
    for (int i=0;i<9;i++) ans=(ans+f[now][add][i])%mod;
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值