【题解】数拆分 & P6189 [NOI Online #1 入门组] 跑步
模拟赛的一道题目
"ppip看到了一个各元素均为正整数、长度为k、各元素之和为n的不降数列。他想要知道这种数列的个数,对998244853取模。"
事实证明,我的写法是:先垫一层 \(1\),再用拆解为正整数的做法去求答案。虽然过了,但感觉自己都不知道自己在干什么。
正解代码:
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244853;
int f[2][100010],n,k,ans;
inline int add(int x,int y){ return x + y >= mod ? x + y - mod : x + y; }
inline void toadd(int &x,int y){ x = add(x,y); }
int main(){
scanf("%d%d",&n,&k);
if(n < k)return puts("0"),0;
else if(n == k)return puts("1"),0;
f[0][0] = 1;
for(int i = 1;i<=k;++i){
int now = (i&1),pre = (now^1);
for(int j = 0;j<=n;++j)f[now][j] = 0;
for(int j = i;j<=n;++j){
toadd(f[now][j],f[pre][j-1]);
toadd(f[now][j],f[now][j-i]);
}
}
printf("%d",f[k&1][n]);
return 0;
}
下面是数拆分的几种类型。
没有限定拆分的个数,可以直接完全背包:
f[0]=1;
for(int i=1;i<=k;i++)
for(int j=i;j<=n;j++)
f[j]=(f[j]+f[j-i])%mod;
printf("%lld\n",f[n]);
限定拆分个数:
// f[i][j] 表示选择了 i 个,总和为 j
f[0][0] = 1
for(int i=1;i<=k;i++)
for(int j=i;j<=n;j++)
f[i][j]=f[i-1][j-1]+f[i][j-i];
理解:每次要么多选一个 \(1\),要么每个数都 \(+1\)。
要求拆出来的数不重复:
g[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
g[i][j]=g[i-1][j-i]+g[i][j-i];
理解:必须选 \(i\),每次要么多放一个 \(i\),要么每个数都加上 \(i\)。
有些感性。相对于每次的增量为 \(1\) 而言,每次增量为 \(i\) 可以保证每个增量都会出现
P6189 [NOI Online #1 入门组] 跑步。
这也是一道数拆分的题目。
本质上就是上面的那个完全背包。但是是考虑到 \(x > \sqrt n\) 的个数 \(< \sqrt n\),采用根号分治解决。
对于 \(x < \sqrt n\) 的部分使用第一类,\(x > \sqrt n\) 的采用第二类,最后合并即可。时间复杂度 \(O(n \sqrt n)\)
题解。

浙公网安备 33010602011771号