【题解】数拆分 & 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)\)
题解

posted @ 2024-08-17 15:49  Luzexxi  阅读(33)  评论(0)    收藏  举报