[题解] [ABC221H] Count Multiset - DP
[ABC221H] Count Multiset
题面翻译
输入两个正整数 ,并存在一个集合,问你一个长度为 的合法集合存在多少个?你需要回答 的值为 到 的每种情况。
一个合法的集合定义指长度为 ,元素和为 ,每一个数字在集合中出现的次数都小于等于 的集合。
题目描述
正の整数 , が与えられます。
について以下の値を求め、 で割ったあまりをそれぞれ出力してください。
- 個の正整数からなる多重集合 のうち、以下の つの条件をすべて満たすものの個数
- に含まれる要素の総和は
- 任意の正整数 について、 は を高々 個しか含まない
输入格式
入力は以下の形式で標準入力から与えられる。
输出格式
行に渡って出力せよ。 行目には、 の場合の答えを出力すること。
样例 #1
样例输入 #1
4 2
样例输出 #1
1 2 1 0
样例 #2
样例输入 #2
7 7
样例输出 #2
1 3 4 3 2 1 1
提示
制約
- 入力はすべて整数
Sample Explanation 1
- のとき、問題文中の条件を満たすような多重集合 は の 通りです。 - のとき、問題文中の条件を満たすような多重集合 は と の 通りです。 - のとき、問題文中の条件を満たすような多重集合 は の 通りです。 - のとき、問題文中の条件を満たすような多重集合 は つも存在しません。
解析
一道神奇的 dp 题。有个神奇的转移方式。
看到题目直接联想到背包,但发现 相同元素不超过 这个限制条件很毒瘤。无法用背包转移。然后死活想不出来其他方法。
根据题解,我们不按照背包的思路想,就把他看成一段序列,元素为非负整数(如果有负数,那么每一种情况都有无穷种方案)。令 表示集合里有 个元素,和为 的方案数。考虑在整个序列里添加 ,那么它只对 有贡献,可以得到:。但只有 0 不够,考虑在序列里的每一个数加 ,那么它只对 有贡献,可以得到:。
但还有个 相同元素不超过 这个限制条件,我们不知道一个 序列里有多少个 ,所以需要再开个数组 表示没有元素为 的方案数。如何让元素不为 呢?把每个元素 即可。于是有:
如果直接 for 硬套的话,复杂度为 。会超。
70 TLE 代码:
#include<bits/stdc++.h> using namespace std; #define int long long constexpr int N = 5010, M = 998244353; int n, m, dp[N][N], g[N][N]; signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n>>m; for(int i=1; i<=m; ++i) dp[i][0] = 1ll; for(int i=1; i<=n; ++i){ for(int j=1; j<=n; ++j){ if(j >= i){ g[i][j] = (g[i][j] + dp[i][j-i]) % M; dp[i][j] = (dp[i][j] + dp[i][j-i]) % M; } for(int k=1; k<=m && i-k>=0; ++k){ dp[i][j] = (dp[i][j] + g[i-k][j]) % M; } } } for(int i=1; i<=n; ++i) cout<<g[i][n]<<'\n'; return 0; }
对于那个最内层循环,可以使用前缀和优化,使复杂度分摊到每一次循环 ,于是总复杂度 ,对了,本题不能开 long long
,会爆空间。
AC code
#include<bits/stdc++.h> using namespace std; constexpr int N = 5001, M = 998244353; int n, m, dp[N][N], g[N][N], sum[N][N]; int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n>>m; for(int i=1; i<=m; ++i) dp[i][0] = 1ll; for(int i=1; i<=n; ++i){ for(int j=1; j<=n; ++j){ if(j >= i){ g[i][j] = (g[i][j] + dp[i][j-i]) % M; dp[i][j] = (dp[i][j] + dp[i][j-i]) % M; } sum[i][j] = (sum[i-1][j] + g[i][j]) % M; dp[i][j] = (__int128)(dp[i][j] + sum[i-1][j] - sum[max(i-m-1, 0)][j]) % M; } } for(int i=1; i<=n; ++i) cout<<g[i][n]<<'\n'; return 0; }
小结:
- 不要过于思维定式。如果发现一个方案不可行或很难行要跳出来另找其他方案。
- 对于求转移方程,可以考虑控制变量,即只对某一变量产生贡献,最后再将贡献合并。很好的思路,只是比较难想。
define int long long
不要再用了。- 计算空间复杂度。
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18295512
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步