[BZOJ1974][Sdoi2010]auction 代码拍卖会(dp+组合数学)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=1974

Solution

看上去是一个显然的数位 dp ,但是看到 N1018 N ≤ 10 18 的数据范围就发现不是那么容易了。
本题的思路最巨之处就在于:
将 1111111…222222…333333…44444…5555… 式的数拆成不到 9 个形如 11111111111 的数之和,并且至少要有一个 111111111111111111111111111…111( N 个 1 ) 。
又由于 11111111111111111...111 11111111111111111...111 形式的数对 P P 取模的值随 1 的个数满足周期性变化,所以我们可以用一个数组 cnt[] c n t [ ] ,储存:
cnt[i] c n t [ i ] 表示 111111111111111111...11111 111111111111111111...11111 11 1 ≤ 1 的个数 N ≤ N )形式的数有多少个。可以利用周期性预处理出。
从小到大考虑 11111111111111111...11 11111111111111111...11 形式的数,可以 dp 辣!
f[i][j][k] f [ i ] [ j ] [ k ] 表示考虑到 cnt[i] c n t [ i ] , 使用 k k 11111...111 形式的数,它们之和模 P P 的值为 j 的方案数
(注意 k k 的上界是 8 不是 9 9 ,最后我们还要加上 111111111111111...111 N N 1))
前置公式: n n 种元素,每种元素有无穷多个,从中取出 m 个元素的不同方案数为 (n+m1m) ( n + m − 1 m )
边界:

f[0][0][k]=(cnt[0]+k1k) f [ 0 ] [ 0 ] [ k ] = ( c n t [ 0 ] + k − 1 k )

注意到 cnt c n t 的值很大但 k k 的值很小,组合数可以预处理。
枚举使用的模 P i i 1111111111111111111...111 的个数 h h 0hk ) ,就能实现转移。
f[i][j][k]=h=0k(cnt[i]+h1h)f[i1][(jihmodP+P)modP][kh] f [ i ] [ j ] [ k ] = ∑ h = 0 k ( c n t [ i ] + h − 1 h ) f [ i − 1 ] [ ( j − i h mod P + P ) mod P ] [ k − h ]

答案:
k=08f[P1][(P(111...111(N1)))modP][k] ∑ k = 0 8 f [ P − 1 ] [ ( P − ( 111...111 ( N 个 1 ) ) ) mod P ] [ k ]

这种拆分成 111111111111111111111111111111111111...111 111111111111111111111111111111111111...111 的思路真的很难想到。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
typedef long long ll; using namespace std;
const int N = 505, M = 1e5 + 5, E = 11, PYZ = 999911659;
int CYX, b[M], f[N][N][E], dalao, orz[N], fac[N], inv[N], otz[N][N];
ll n, cnt[N];
int qpow(int a, int b) {
    int res = 1; while (b)  b & 1 ? res = 1ll * res * a % PYZ : 0,
        a = 1ll * a * a % PYZ, b >>= 1; return res;
}
int main() {
    int i, j, k, h, rep, len, gap, ans = 0;
    cin >> n >> CYX; For (i, 1, 2147483647) {
        b[i] = (b[i - 1] * 10 + 1) % CYX;
        if (orz[b[i]]) {rep = orz[b[i]]; len = i; break;}
        orz[b[i]] = i;
    }
    For (i, 1, min(1ll * rep - 1, n)) cnt[b[i]]++;
    gap = len - rep; if (rep <= n) For (i, rep, len - 1) {
        cnt[b[i]] += (n / gap + (i % gap <= n % gap))
            - ((rep - 1) / gap + (i % gap <= (rep - 1) % gap));
        if (n % gap == i % gap) dalao = b[i];
    }
    fac[0] = 1; For (i, 1, 20) fac[i] = 1ll * fac[i - 1] * i % PYZ;
    inv[20] = qpow(fac[20], PYZ - 2); Rof (i, 19, 0)
        inv[i] = 1ll * inv[i + 1] * (i + 1) % PYZ;
    For (i, 0, CYX - 1) {
        if (!cnt[i]) {
            otz[i][0] = 1; For (j, 1, 20) otz[i][j] = 0; continue;
        }
        otz[i][0] = 1; For (j, 1, 20) otz[i][j] = 1ll * otz[i][j - 1]
            * ((cnt[i] + j - 1) % PYZ) % PYZ;
        For (j, 1, 20) otz[i][j] = 1ll * otz[i][j] * inv[j] % PYZ;
    }
    For (i, 0, 8) f[0][0][i] = otz[0][i];
    For (i, 1, CYX - 1) For (j, 0, CYX - 1) For (k, 0, 8) For (h, 0, k)
        f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1]
            [(j - i * h % CYX + CYX) % CYX][k - h] * otz[i][h] % PYZ) % PYZ;
    For (i, 0, 8) ans = (ans + f[CYX - 1][(CYX - dalao) % CYX][i]) % PYZ;
    cout << ans << endl; return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值