BZOJ 1974 [Sdoi2010]auction 代码拍卖会 | 51nod 1261 上升数

3 篇文章 0 订阅

题目:
http://www.lydsy.com/JudgeOnline/problem.php?id=1974
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1261

题意:
一个10进制表示的正整数,如果从左到右,每一位的数字都不小于前一位的数字,则被称为上升数。
给出正整数 N K,求有多少个长度恰好为 N 的上升数是K的倍数,答案对 999911659 取模。
N1018,K500

题解:
上升数可以拆成不超过9个类似 10k19 的数字的和,这些数字 (1,11,111,1111,) 在模 K 意义下的值有O(K)种取值,可以将这些数分成 O(K) 组,进行分组背包计数。
f[i][j][k] 表示前 i 组里选j个数的和在模 K 意义下是k的方案数,由于同一组里选 m 个数组成可重集的方案数是(X1+mm),其中 X 表示组里不同数字的个数,所以转移是枚举当前组里选多少个数进行转移。
注意10N19至少要选1个,所以将它从上面的计数里排除,最后枚举它的数量,再配合之前计数的结果进行计数即可。
时间复杂度 O(102K2) ,数组滚动后空间复杂度 O(10K)
注意 K=1 的情况。

代码:

#include <cstdio>
typedef long long LL;
const int maxm = 510, maxd = 10, mod = 999911659;
LL n;
int m, a[maxm], pos[maxm], s[maxm], val, inv[maxd], f[maxd][maxm], ans;
int main()
{
    scanf("%lld%d", &n, &m);
    a[1] = 1 % m;
    pos[a[1]] = 1;
    for(int i = 2; i <= m + 1; ++i)
    {
        a[i] = (a[i - 1] * 10 + 1) % m;
        if(pos[a[i]])
        {
            int beg = pos[a[i]], end = i, len = end - beg;
            for(int j = 1; j < beg && j <= n; ++j)
                ++s[a[j]];
            for(int j = 0; j < len && beg + j <= n; ++j)
                s[a[beg + j]] = (s[a[beg + j]] + (n - beg - j) / len + 1) % mod;
            val = n < beg ? a[n] : a[beg + (n - beg) % len];
            --s[val];
            if(s[val] < 0)
                s[val] += mod;
            break;
        }
        pos[a[i]] = i;
    }
    inv[1] = 1;
    for(int i = 2; i < maxd; ++i)
        inv[i] = mod - mod / i * (LL)inv[mod % i] % mod;
    f[0][0] = 1;
    for(int i = 0; i < m; ++i)
    {
        if(!s[i])
            continue;
        for(int j = maxd - 1; j > 0; --j)
        {
            int coeff = 1;
            for(int k = 1; k <= j && coeff; ++k)
            {
                coeff = (LL)coeff * (s[i] - 1 + k) % mod * inv[k] % mod;
                for(int o = 0; o < m; ++o)
                    f[j][(o + k * i) % m] = (f[j][(o + k * i) % m] + (LL)coeff * f[j - k][o]) % mod;
            }
        }
    }
    for(int i = 1; i < maxd; ++i)
    {
        int res = val * i % m;
        if(res)
            res = m - res;
        for(int j = 0; i + j < maxd; ++j)
        {
            ans += f[j][res];
            if(ans >= mod)
                ans -= mod;
        }
    }
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值