[NOI Online #1 入门组] 跑步

题目描述

小 H 是一个热爱运动的孩子,某天他想给自己制定一个跑步计划。小 H 计划跑n米,其中第i(i \geq 1)分钟要跑 x_i米(x_i 是正整数),但没有确定好总时长。

由于随着跑步时间增加,小 H 会越来越累,所以小 H 的计划必须满足对于任意i(i >1)都满足x_i \leq x_{i-1}

现在小 H 想知道一共有多少个不同的满足条件的计划,请你帮助他。两个计划不同当且仅当跑步的总时长不同,或者存在一个i,使得两个计划中x_i不相同。

由于最后的答案可能很大,你只需要求出答案对 p 取模的结果。

输入格式

输入只有一行两个整数,代表总米数n和模数 p。

输出格式

输出一行一个整数,代表答案对p取模的结果。

输入输出样例

输入 #1

4 44

输出 #1

5

输入 #2

66 666666

输出 #2

323522

输入 #3

66666 66666666

输出 #3

45183149

说明/提示

样例输入输出 1 解释

五个不同的计划分别是:\{1,1,1,1\}\{2,1,1\}\{3,1\}\{2,2\}\{4\}


数据规模与约定

本题共 10个测试点,各测试点信息如下表。

测试点编号n≤测试点编号n≤
15562\times 10^3
21075\times 10^3
35082\times 10^4
410095\times 10^4
55001010^5

对于全部的测试点,保证 1 \leq n \leq 10^51 \leq p < 2^{30}

本题就是正整数拆分问题,很容易想到一种O(n^2)的 DP:

f_{i},j​ 表示用了前 i 个数和为 j的方案数,那么 f_{i,j}=f_{i-1,j-i}+f_{i,j-i},初始时f_{0,0}=1 。

这其实就是完全背包,但只能拿 70分(也可能乱搞到 80分)。

那么怎么办呢?我们采用分块的思路,令 m=\sqrt n,对于 i<m 的数采用以上完全背包来求,这部分时间复杂度为 O(n\sqrt n)

而对于大于等于 m的数,采用另外一种 DP:

记 g_{i,j}表示用了i个大于等于m的数和为j的方案数,那么g_{i,j}=g_{i-1,j-m}+g_{i,j-i}gi​,初始时g_{0,0}=1 。

  • 转移方程的第一项 g_{i-1,j-m} 表示在拆分序列中增加一个数m。
  • 转移方程的第二项 g_{i,j-i}表示把当前拆分序列的每个数都加上 11。

i最大为 g_{i-1,j-m},因而这一步时间复杂度为 O(n\sqrt n)

如果觉得不好理解,可以随便举个例子:

假设当前 m=1,你要拆的数是 11,其中一种方案是 11=5+2+2+2。初始拆分序列为空,现在有两种操作:操作 1是在拆分序列中增加一个数1,操作2 是把当前序列中每个数都增加1 。那么该方案对应的操作序列为:122221112。

容易发现,一种拆分方案和一种操作序列是一一对应的,因此这样 DP 不会重复也不会遗漏。

最后一步,就是把分块的两部分结合一下。枚举第一个部分的总和为 jj,那第二个部分的总和就为 n-j,把两者的方案数乘起来记入答案中,即为\sum\limits_{j=0}^n (f_{m-1,j}\times \sum\limits_{i=0}^{m} g_{i,n-j})

那么本题总时间复杂度为 O(n\sqrt n),代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;

int f[N];
int g[400][N];
int main() {
    int n, p;
    cin >> n >> p;
    int m = sqrt(n) + 1;
    f[0] = 1;
    for (int i = 1; i < m; i++) {
        for (int j = i; j <= n; j++) {
            f[j] += f[j - i];
            f[j] %= p;
        }
    }
    g[0][0] = 1;
    for (int i = 1; i < m; i++) {
        for (int j = i; j <= n; j++) {
            g[i][j] = g[i][j - i];
            if (j >= m) g[i][j] += g[i - 1][j - m];
            g[i][j] %= p;
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) {
        LL sum = 0;
        for (int j = 0; j < m; j++) sum += g[j][n - i];
        sum %= p;
        ans = (ans + f[i] * sum) % p;
    }
    cout << ans << endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值