AtCoder Beginner Contest 221 H. Count Multiset(容斥 dp 拆分数 差分 数形结合)

本文主要是介绍AtCoder Beginner Contest 221 H. Count Multiset(容斥 dp 拆分数 差分 数形结合),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目

给定m,n(m<=n<=5e3),

求大小为k的多重集合,满足元素和为n,

且每种数在集合中出现的次数都小于等于m的集合数有多少个

答案对998244353取模

思路来源

官方题解

「解题报告」[ABC221H] Count Multiset - K8He - 洛谷博客

Solution-ABC221H - yllcm 的博客 - 洛谷博客

【AtCoder思维训练】ABC221H Count Multiset - QAQ - 洛谷博客

题解1

整体来说,如果没有每个次数<=m的限制,就是分拆数

1. 把多重集合转成不下降序列(单增序列),

每个序列统计一次(A1,A2,...,Ak)(A1<=A2<=...<=Ak)

2. 把不下降序列转成差分数组,

令B[1]=A[1],B[i]=A[i]-A[i-1],

对于差分数组,需要满足以下三个条件:

\sum_{i=1}^{k}B_{i}*(k+1-i)=n

②B数组中不存在连续的m个0

B_{1}>0

3. 发现①做dp的时候是有后效性的,

与k相关, 第k+1次的时候需要加上前k个的和

考虑对差分数组反转,即令i=k+1-i

反转后的差分数组B,需要满足以下三个条件:

\sum_{i=1}^{k}B_{i}*i=n

②B数组中不存在连续的m个0

B_{k}>0

f[i][j]表示当前选了i个数,总和为j的方案数

①一种方式是,对反转的差分序列后面新增一个0,

如:

原序列2 2 3,差分序列2 0 1,反转差分序列1 0 2,

此时给反转差分序列后面加一个0,得到1 0 2 0,

对应差分序列0 2 0 1,原序列0 2 2 3,即原序列前面加一个0

即f[i][j]从f[i-1][j]转移而来

②另一种方式是,对反转的差分序列的最后一个数加1,

如:

原序列2 2 3,差分序列2 0 1,反转差分序列1 0 2,

此时给反转差分序列最后一个数加1,得到1 0 3,

对应差分序列3 0 1,原序列3 3 4,即原序列整体加1

即f[i][j]从f[i][j-i]转移而来

考虑怎么加上连续最多m个0的限制,

设g[i][j]表示当前填了i个数,总和为j,序列里不含0的方案数,

给整体加1过后的序列,即不包含0,有g[i][j]=f[i][j-i]

f的转移,要么是对f数组整体加1,

要么是钦定0的个数,从一段没有0的g数组转移过来

f[i][j]=f[i][j-i]+\sum_{k=1}^mg[i-k][j]

g[i][j]=f[i][j-i]

由于序列里没有0,最后g[i][n]即为所求

当然,可以进一步化简,

f[i][j]=\sum_{k=0}^mg[i-k][j]

g[i][j]=f[i][j-i]

因为最后是求g数组,可以上式代入下式联立消掉f,有

g[i][j]=f[i][j-i]=\sum_{k=0}^mg[i-k][j-i]

就与官方题解中的代码一致了,前缀和优化一下,复杂度O(n^2)

题解2

接题解1,反转后的差分数组B,需要满足以下三个条件:

\sum_{i=1}^{k}B_{i}*i=n

②B数组中不存在连续的m个0

B_{k}>0

直接g[i][j]表示当前选了i个数,\sum_{x=1}^{i}B_{x}*x=j,最后一个数即b[i]>0的方案数

考虑暴力转移,

从1到m,枚举最后一段0的连续段长度,

也就是枚举上一个非0的位置x,再枚举b[i]选择的数为w,有:

g[i][j]=\sum_{w=1}^{w*i\leq j}\sum_{x=i-m}^{i-1}g[x][j-w*i]

\sum_{x=i-m}^{i-1}g[x][j-w*i]的第一维,也就是g[x]这一维维护前缀和,

即可实现转移,复杂度O(n^2logn)

题解3

考虑直接对原序列做dp,

f[i][j]表示前i个数和为j的方案数

如:原序列1 1 2,

①每次要么新增一个1,转移到1 1 1 2,f[i][j]从f[i-1][j-1]转移

②要么令所有数都+1,使得所有数都大于等于2,转移到2 2 3,f[i][j]从f[i][j-i]转移

但是,第一种转移新增了一个1,可能会导致恰出现连续m+1个1的情况,减掉这种情况即可

出现这种情况时,前m+1个数字为1,且第m+2个数为>=2的值,

只需全局减1,即可删掉m+1个1,并且使得第m+2个数的值>=1,也就对应了f[i-(m+1)][j-i]

f[i][j]=f[i-1][j-1]+f[i][j-i]-f[i-(m+1)][j-i]

复杂度O(n^2)

题解4

数形结合,

如果对原序列dp,如下图所示,有三条限制,

x_{i}\leq x_{i+1},x_{1}> 0

②不存在超过m个xi相同

\sum x_{i}=n

按照箭头视角去看这个图,

也就是先顺时针旋转90度,再翻转,

新的序列仍然有三条限制,

y_{i}\leq y_{i+1},y_{1}> 0

y_{i+1}-y_{i}\leq m

\sum y_{i}=n

发现限制2更强了,所以可以对新序列dp,

dp[i][j]表示最后一列高为i,柱状图面积总和为j的方案数,

枚举上一列高为x,需要满足x∈[i-m,i],有:

dp[i][j]=\sum_{x\leq i,x\geq i-m}dp[x][j-i]

惊奇地发现,这和题解1得到的转移式子一模一样

复杂度O(n^2)

代码1、代码4 O(n^2)

#include<iostream>
using namespace std;
const int N=5e3+10,mod=998244353;
int n,m,dp[N][N],sum[N][N];
void add(int &x,int y){x=(x+y)%mod;
}
int main(){scanf("%d%d",&n,&m);dp[0][0]=sum[0][0]=1;for(int i=1;i<=n;++i){for(int j=0;j<=n;++j){if(j>=i){dp[i][j]=sum[i][j-i];if(i-m-1>=0){add(dp[i][j],mod-sum[i-m-1][j-i]);}}sum[i][j]=(sum[i-1][j]+dp[i][j])%mod;}}for(int i=1;i<=n;++i){printf("%d\n",dp[i][n]);}return 0;
}

代码2 O(n^2logn)

#include<iostream>
using namespace std;
const int N=5e3+10,mod=998244353;
int n,m,g[N][N],sum[N][N];
void add(int &x,int y){x=(x+y)%mod;
}
int main(){scanf("%d%d",&n,&m);g[0][0]=sum[0][0]=1;for(int i=1;i<=n;++i){for(int j=0;j<=n;++j){for(int w=1;w*i<=j;++w){add(g[i][j],sum[i-1][j-w*i]);if(i-m-1>=0)add(g[i][j],mod-sum[i-m-1][j-w*i]);}sum[i][j]=(sum[i-1][j]+g[i][j])%mod;}}for(int i=1;i<=n;++i){printf("%d\n",g[i][n]);}return 0;
}

代码3 O(n^2)

#include<iostream>
using namespace std;
const int N=5e3+10,mod=998244353;
int n,m,dp[N][N];
void add(int &x,int y){x=(x+y)%mod;
}
int main(){scanf("%d%d",&n,&m);dp[0][0]=1;for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){dp[i][j]=dp[i-1][j-1];if(j-i>=0)add(dp[i][j],dp[i][j-i]);if(i>=m+1 && j-i>=0)add(dp[i][j],mod-dp[i-(m+1)][j-i]);}}for(int i=1;i<=n;++i){printf("%d\n",dp[i][n]);}return 0;
}

代码5 O(n^3)

自己乱搞了两个复杂度并不正确的做法,也贴在这里好了

这个是考虑容斥减掉不合法的答案

#include<iostream>
using namespace std;
const int N=5e3+10,mod=998244353;
typedef long long ll;
int n,m,dp[N][N],sum[N];//dp[i][j]选了i个和为j方案数
void add(int &x,int y){x=(x+y)%mod;}
int main(){scanf("%d%d",&n,&m);dp[0][0]=1;for(int l=1;l<=n;++l){for(int i=1;i<=n;++i){for(int j=l;j<=n;++j){add(dp[i][j],dp[i-1][j-l]);/*for(int k=1;k<=j;++k){add(dp[i][j],dp[i-1][j-k]);}*/}}for(int i=n;i>=m+1;--i){for(int j=n;j-l*(m+1)>=0;--j){add(dp[i][j],mod-dp[i-(m+1)][j-l*(m+1)]);   }}}// for(int i=1;i<=n;++i){//     for(int j=1;j<=n;++j){//         printf("i:%d j:%d dp:%d\n",i,j,dp[i][j]);//     }// }for(int i=1;i<=n;++i){printf("%d\n",dp[i][n]);}return 0;
}

代码6 O(n^3logn)

这个是纯纯暴力

#include<iostream>
using namespace std;
const int N=5e3+10,mod=998244353;
typedef long long ll;
int n,m,dp[N][N];//dp[i][j]选了i个和为j方案数
void add(int &x,int y){x=(x+y)%mod;}
int main(){scanf("%d%d",&n,&m);dp[0][0]=1;for(int i=1;i<=n;++i){for(int j=n;j>=i;--j){for(int k=1;k<=m;++k){if(j-k*i<0)break;for(int l=n;l>=k;--l){add(dp[l][j],dp[l-k][j-k*i]);}}}}for(int i=1;i<=n;++i){printf("%d\n",dp[i][n]);}return 0;
}

这篇关于AtCoder Beginner Contest 221 H. Count Multiset(容斥 dp 拆分数 差分 数形结合)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/632722

相关文章

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

poj 3159 (spfa差分约束最短路) poj 1201

poj 3159: 题意: 每次给出b比a多不多于c个糖果,求n最多比1多多少个糖果。 解析: 差分约束。 这个博客讲差分约束讲的比较好: http://www.cnblogs.com/void/archive/2011/08/26/2153928.html 套个spfa。 代码: #include <iostream>#include <cstdio>#i

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs

uva 10069 DP + 大数加法

代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>#include <cl

uva 10029 HASH + DP

题意: 给一个字典,里面有好多单词。单词可以由增加、删除、变换,变成另一个单词,问能变换的最长单词长度。 解析: HASH+dp 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc