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

news2024/9/25 15:28:26

题目

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1403624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试不拘一格——掌握Pytest插件pytest-random-order

在测试领域&#xff0c;测试用例的执行顺序往往是一个重要的考虑因素。Pytest插件 pytest-random-order 提供了一种有趣且灵活的方式&#xff0c;让你的测试用例能够以随机顺序执行。本文将深入介绍 pytest-random-order 插件的基本用法和实际案例&#xff0c;助你摆脱固定的测…

CSGO搬砖项目还能火多久?

最近放假回到老家&#xff0c;见了不少亲戚朋友&#xff0c;大家不约而同都在感叹今年大环境不好&#xff0c;工作不顺&#xff0c;生意效益不好&#xff0c;公司状况不佳&#xff0c;反问我们生意如何&#xff1f;为了让他们心里好受一点&#xff0c;我也假装附和道:也不咋地&…

超简单的OCR模块:cnocr

前言 毫无疑问的是&#xff0c;关于人工智能方向&#xff0c;python真的十分方便和有效。 这里呢&#xff0c;我将介绍python众多OCR模块中一个比较出色的模块&#xff1a;cnocr 模块介绍 cnocr是一个基于PyTorch的开源OCR库&#xff0c;它提供了一系列功能强大的中文OCR模型和…

常用芯片学习——HC245芯片

HC245三态输出八路总线收发器 使用说明 这些八路总线收发器专为数据总线之间的异步双向通信而设计。控制功能实现可更大限度地减少外部时序要求。根据方向控制 (DIR) 输入上的逻辑电平&#xff0c;此类器件将数据从 A 总线发送至 B 总线&#xff0c;或者将数据从 B 总线发送至…

使用torch实现RNN

在实验室的项目遇到了困难&#xff0c;弄不明白LSTM的原理。到网上搜索&#xff0c;发现LSTM是RNN的变种&#xff0c;那就从RNN开始学吧。 带隐藏状态的RNN可以用下面两个公式来表示&#xff1a; 可以看出&#xff0c;一个RNN的参数有W_xh&#xff0c;W_hh&#xff0c;b_h&am…

c语言->学会offsetof宏计算结构体相对偏移量

前言 ✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 目的&#xff0c;学习offsetof宏计算结构体相对偏移量 1.offsetof宏 来我们看图…

2.服务拆分和远程调用

2.服务拆分和远程调用 任何分布式架构都离不开服务的拆分&#xff0c;微服务也是一样。 2.1.服务拆分原则 这里我总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将…

基于springboot vue实现的医院管理系统

一、 项目介绍 角色&#xff1a;管理员、患者、医生 基于springboot vue实现的医院管理系统&#xff0c;有管理员、医生和患者三种角色。系统拥有丰富的功能&#xff0c;能够满足各类用户的需求&#xff0c;系统提供了登录和注册功能&#xff0c;确保用户的信息安全和权限管理。…

类于对象下

再识构造函数——初始化列表——成员变量定义的地方 之前我们说&#xff1a;构造函数&#xff0c;对自定义类型会去调用他的默认构造函数&#xff0c;但是如果内置类型也没有写构造函数呢&#xff1f; 这里我们引用出——初始化列表&#xff08;也是一种构造函数&#xff09;…

四、RHCE--远程连接服务器

四、RHCE--远程连接服务器 1、远程连接服务器简介2、连接加密技术简介&#xff08;1&#xff09;版本协商阶段&#xff08;2&#xff09;密钥和算法协商阶段&#xff08;3&#xff09;认证阶段 3、ssh服务配置4、用户登录ssh服务器 1、远程连接服务器简介 &#xff08;1&#…

【算法竞赛C++STL基础】栈,链表,队列,优先队列,map,set以及迭代器的用法

文章目录 1&#xff0c;前知——模板函数的实现2, hash 表1&#xff0c;定义2,ASCII码表3&#xff0c;咉射关系 3&#xff0c;迭代器4&#xff0c;STL关系1,stl 的基础关系2,stl 的分类1,相关分类2,相关简介顺序容器关联容器适配容器 3. STL 的相关函数的学习3.1 STL函数中都含…

vue项目编译非常慢,经常卡在某个百分点

1、注册插件 2、在项目根目录下的 babel.config.js 文件中加入下方配置 3、将import导入方式改为require导入方式&#xff0c;返回promise 4、如果动态加载组件import引入组件找不到组件&#xff08;Error: Cannot find module&#xff09; 使用 webpack 的 require.ensure() …

每天五分钟计算机视觉:掌握迁移学习使用技巧

本文重点 随着深度学习的发展,迁移学习已成为一种流行的机器学习方法,它能够将预训练模型应用于各种任务,从而实现快速模型训练和优化。然而,要想充分利用迁移学习的优势,我们需要掌握一些关键技巧。本文将介绍这些技巧,帮助您更好地应用迁移学习技术。 迁移学习的关键…

UG制图-视图与投影

当我们进入图纸页后&#xff0c;我们需要对产品进行投影然后进行标注 注意&#xff1a;如果是从零件3D中直接进入制图&#xff0c;默认情况下图框所在的图层是不显示的&#xff0c;我们可以通过菜单或者快捷键ctrl L进入图层设置模块&#xff0c;将图层170和173勾选为显示 我…

MySQL JSON数据类型

在日常开发中&#xff0c;我们经常会在 MySQL 中使用 JSON 字段&#xff0c;比如很多表中都有 extra 字段&#xff0c;用来记录一些特殊字段&#xff0c;通过这种方式不需要更改表结构&#xff0c;使用相对灵活。 目前对于 JSON 字段的实践各个项目不尽相同&#xff0c;MySQL 表…

SpringCloud Alibaba 深入源码 - Nacos 分级存储模型、支撑百万服务注册压力、解决并发读写问题(CopyOnWrite)

目录 一、SpringCloudAlibaba 源码分析 1.1、SpringCloud & SpringCloudAlibaba 常用组件 1.2、Nacos的服务注册表结构是怎样的&#xff1f; 1.2.1、Nacos的分级存储模型&#xff08;理论层&#xff09; 1.2.2、Nacos 源码启动&#xff08;准备工作&#xff09; 1.2.…

windows11上安装虚拟机VMware

1、安装虚拟机&#xff08;待补充&#xff09; 第二步&#xff1a;安装VMware tools 实现windows文件上传到虚拟机中 1、安装好虚拟机后&#xff0c;查看虚拟机ip用Xshell连接虚拟机&#xff0c;并安装VMware tools(只有安装了VMware tools才能实现虚拟机和本机的文件共享。在…

P4学习(六)实验三:a Control Plane using P4Runtime

目录 一. 实验目的二.阅读MyController.py文件1.导入P4Runtime的库2.main部分1. P4InfoHelper 实例化2. 创建交换机连接3. 设置主控制器4. 安装 P4 程序5. 写入隧道规则6. 读取表项和计数器&#xff08;注释掉的部分&#xff09;7. 定时打印隧道计数器8. 异常处理9. 关闭交换机…

动态规划系列问题之打家劫舍和买股票

动态规划系列问题 1.打家劫舍问题1.1打家劫舍I1.2打家劫舍II1.3打家劫舍III 2.买股票问题2.1买股票的最佳时机2.2买股票的最佳时机II2.3买股票的最佳时机III2.4买股票的最佳时机IV2.5买卖股票的最佳时机含冷冻期2.6买卖股票的最佳时机含手续费 题目解析参考了代码随想录 https:…

【Redis漏洞利用总结】

前言 redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。Redis默认使用 6379 端口。 一、redis未授权访问漏洞 0x01 漏洞描述 描述: Redis是一套开源的使用ANSI C编写、支持网络、可基于内存…