Welcome to|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

2024-07-11 10:19阅读: 15评论: 0推荐: 0

[题解] [ABC221H] Count Multiset - DP

[ABC221H] Count Multiset

题面翻译

输入两个正整数 N,M,并存在一个集合,问你一个长度为 k 的合法集合存在多少个?你需要回答 k 的值为 1N 的每种情况。

一个合法的集合定义指长度为 k,元素和为 N,每一个数字在集合中出现的次数都小于等于 M 的集合。

题目描述

正の整数 N, M が与えられます。

k=1,2,,N について以下の値を求め、998244353 で割ったあまりをそれぞれ出力してください。

  • k 個の正整数からなる多重集合 A のうち、以下の 2 つの条件をすべて満たすものの個数
    • A に含まれる要素の総和は N
    • 任意の正整数 x について、Ax を高々 M 個しか含まない

输入格式

入力は以下の形式で標準入力から与えられる。

N M

输出格式

N 行に渡って出力せよ。i (1  i  N) 行目には、k=i の場合の答えを出力すること。

样例 #1

样例输入 #1

4 2

样例输出 #1

1
2
1
0

样例 #2

样例输入 #2

7 7

样例输出 #2

1
3
4
3
2
1
1

提示

制約

  • 1  M  N  5000
  • 入力はすべて整数

Sample Explanation 1

- k=1 のとき、問題文中の条件を満たすような多重集合 A41 通りです。 - k=2 のとき、問題文中の条件を満たすような多重集合 A1,32,22 通りです。 - k=3 のとき、問題文中の条件を満たすような多重集合 A1,1,21 通りです。 - k=4 のとき、問題文中の条件を満たすような多重集合 A1 つも存在しません。

解析

一道神奇的 dp 题。有个神奇的转移方式。

看到题目直接联想到背包,但发现 相同元素不超过 m 这个限制条件很毒瘤。无法用背包转移。然后死活想不出来其他方法。

根据题解,我们不按照背包的思路想,就把他看成一段序列,元素为非负整数(如果有负数,那么每一种情况都有无穷种方案)。令 dp[i][j] 表示集合里有 i 个元素,和为 j 的方案数。考虑在整个序列里添加 0,那么它只对 i 有贡献,可以得到:dp[i][j]+=k=1mdp[ik][j]。但只有 0 不够,考虑在序列里的每一个数加 1,那么它只对 j 有贡献,可以得到:dp[i][j]+=dp[i][ji]

但还有个 相同元素不超过 m 这个限制条件,我们不知道一个 dp 序列里有多少个 0,所以需要再开个数组 g[i][j] 表示没有元素为 0 的方案数。如何让元素不为 0 呢?把每个元素 +1 即可。于是有:

dp[i][j]=dp[i][ji]+k=1mdp[ik][j]g[i][j]=dp[i][j1]

如果直接 for 硬套的话,复杂度为 O(N2M)。会超。

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;
}

对于那个最内层循环,可以使用前缀和优化,使复杂度分摊到每一次循环 O(1),于是总复杂度 O(N2),对了,本题不能开 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 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(15)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起