[NOI Online #1 入门组] 跑步 题解

[NOI Online #1 入门组] 跑步 题解

一个经典问题:计数将正整数n拆分为若干个正整数的方案数,这里拆成的正整数是无序的,对P取模。

容易得到O(n2)解法

fi,j表示用j个数得到i的方案数。转移即考虑增加一个数为1或全部数加1

或者设fi,j表示用j的数得到i的方案数。转移即考虑选不选j

然而似乎无法继续优化。

下面就是本题的关键了。画出Ferrers图,如图

这里将一行看成一个数。

考虑上面的O(n2)做法在Ferrers图上的实质。

第一种做法是每次在下方加一个点或在左方加一列。复杂度为O(n×)

第二种做法是每次加一行或者将当前可加行长度加一。复杂度为O(n×)

我们想要将行数和列数同时控制在一个合适的范围内,这样把两个做法拼起来或许就能优化。

于是考虑强制第一种做法中每加一个点就加B个,第二种做法中至多将当前可加行长度累加至B1

这样第一种做法中行数至多为nB复杂度就是O(n2B+nB)

B=n,可得最优复杂度为O(nn)

点我看代码 |ू・ω・` )
#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
template <typename T>
inline void read(T &x) {
x = 0; int f = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
if(f) x = ~x + 1;
}
const int B = 317;
const int N = 1e5 + 10;
int n, P, m, ans;
int f[N][B + 10], g[N][B + 10];
LL sg[N];
inline void update(int &x, int y) {if((x += y) >= P) x -= P;}
int main() {
read(n), read(P);
for(int i = 0; i <= B; ++i) f[0][i] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j < B; ++j) {
f[i][j] = f[i][j - 1];
if(i >= j) update(f[i][j], f[i - j][j]);
}
}
m = n / B + 1;
sg[0] = g[0][0] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(i >= B) update(g[i][j], g[i - B][j - 1]);
if(i >= j) update(g[i][j], g[i - j][j]);
sg[i] += g[i][j];
}
sg[i] %= P;
}
for(int i = 0; i <= n; ++i)
update(ans, f[i][B - 1] * sg[n - i] % P);
printf("%d\n",ans);
}
posted @   DCH233  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示