bzoj1974代码拍卖会 & 51nod1261上升数

题意

一个10进制表示的正整数,如果从左到右,每一位的数字都不小于前一位的数字,则被称为上升数。
给出长度N和一个数K,求有多少个长度恰好为N的上升数,是K的倍数。
1<=N<=10182<=K<=500 1 <= N <= 10 1 8 , 2 <= K <= 500

题解

想了很久,一开始想到了一个 O(k294logn) O ( k 2 ∗ 9 4 ∗ l o g n ) 的做法
然后想尽了了办法优化为了 O(k293logn) O ( k 2 ∗ 9 3 ∗ l o g n ) 最后在51nodT炸了
然后怎么想都不知道怎么把 93 9 3 降下来
在这里还是写一下吧。。毕竟是自己想出来的,如果 k k 放小一些可能还可以做不同的情况
我们考虑到n很大,于是我们就考虑二分合并啊
就是,我们要求n的答案,不妨先求n/2的答案,然后两个合起来
怎么合呢?
我的想法是我们要知道前后两端modk的值,分别记为 a,b a , b
那么新的串的余数就是 a10n/2+b a ∗ 10 n / 2 + b
然后就可以愉快地转移了
但是我们发现,我们还要符合上升数
于是只好再加多两维,表示这个串的开头和结尾是什么
然后转移就要枚举两个串的三个量来转移
然后就愉快GG了

真的题解

然后就膜了题解
发现,题解很巧妙
题解是把上升数当做性质
而我是当做限制,区别很大
我们考虑是上升数,所以这个数可以表示成不超过9个”1,11,111,1111…111111”的和
比如说 135=111+11+11+1+1 135 = 111 + 11 + 11 + 1 + 1
然后显然地,11111…1111modk是有循环节的
于是我们可以把余数一样的分成一组
然后就可以DP了
f[u][i][j]ui f [ u ] [ i ] [ j ] 表 示 用 到 了 余 数 为 u 的 这 一 组 , 当 前 用 了 i 个 数 了 , 模 数 是 多 少
然后胡乱转移就可以了
要学习的是处理上升数的这种方式,把限制变为性质就可以使复杂度降下来了
时间复杂度是 O(k292) O ( k 2 ∗ 9 2 ) 的,并且和n都没有关系了
CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const LL MOD=999911659;
const LL K=505;
LL n,k;
LL cnt[K];//有多少种 
LL g[K];
LL now=0;
LL f[K][10][K];//现在用到第几个余数   已经用了多少个了 n个1不算   余数是多少 
LL inv[10];
void add (LL &x,LL y)   {x=(x+y)%MOD;}
int main()
{
    scanf("%lld%lld",&n,&k);
    cnt[0]++;
    if (n<=k)
    {
        for (LL u=1;u<=n;u++)   {now=(now*10+1)%k;cnt[now]++;}
    }
    else
    {
        LL lalal,len;
        for (LL u=1;u<=k+1;u++)
        {
            now=(now*10+1)%k;
            if (cnt[now]!=0)    {lalal=g[now];len=u-g[now];break;}
            cnt[now]++;
            g[now]=u;
        }
        //printf("%lld %lld\n",lalal,len);
        for (LL u=0;u<k;u++)
        {
            if (cnt[u]>0&&g[u]>=lalal)//这个会出现在循环里面 
            {
                cnt[u]=(n-lalal+1)/len;
                if ((g[u]-lalal+1)<=(n-lalal+1)%len)//不完整的循环
                    cnt[u]++;
                if ((g[u]-lalal+1)%len==(n-lalal+1)%len)
                    now=u;//最后一个一定要用 
            }
        }
    }
    inv[1]=1;
    for (LL u=2;u<=9;u++)   inv[u]=MOD-(MOD/u)*inv[MOD%u]%MOD;
    f[0][1][now]=1;
    for (LL u=0;u<k;u++)
        for (LL i=1;i<=9;i++)//转移
            for (LL j=0;j<k;j++)
            {
                LL lalal=1;
                //C(cnt+l-1,l);
                for (LL l=0;l<=i;l++) //用多少个
                {
                    add(f[u+1][i][j],f[u][i-l][(j-l*u%k+k)%k]*lalal%MOD);
                    lalal=lalal*((cnt[u]+l)%MOD)%MOD*inv[l+1]%MOD;
                }
            }
    printf("%lld\n",f[k][9][0]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值