LOJ#6089 小 Y 的背包计数问题 - DP精题
题面

题解
(本篇文章深度剖析,若想尽快做出题的看官可以参考知名博主某C202044zxy的这篇题解:https://blog.csdn.net/C202044zxy/article/details/109141757)
在起码了解了背包DP后,我们来做这道题
既然每种物品最大可占据 i*i 的空间,设
那么 和
两种情况肯定是不同的 ,而且应该可以分开处理
由于 时 i 的范围很小,可以在一个处理到一半的背包上(即处理了[sq+1,n]的背包dp数组)继续
跑(背包大小 * 物品数),所以我们倒着处理背包,先做
的背包
[sq+1 , n]的处理
我原先是这么想的,
对于[sq+1 , n]中的任意一个 i ,肯定都有 i * i > n ,也就是说,每种物品最多也就能装下 i - 1 个,
那不妨就设 [sq+1 , n] 每种物品无穷多个,肯定不影响答案,于是后面部分就可以做完全背包了 ,成功省掉个数限制
但是这样只能过一半的分。
于是我来到NDSC看了 好几 篇题解, 终于 明白怎么做的了,
由于我们一般的背包DP本质上是一个二维的状态 dp[i][j] 表示进行到前 i 个物品,总大小为 j 的~~~,只是一般有用的都是最终的dp[n][j],所以第一维都滚动或者直接扔掉了
而这个部分的背包我们如果还是这么想的话,那就还是有 (n-sq) * n 种状态,
但是我们可以这样定义:dp[i][j] 为总共装了 i 件物品后,总大小为 j 的方案数,
因为这里每个物品大小起码都是 sq+1 ,所以最多装 sq 件物品,还不到,状态数就只有 sq * n ,大大减小
怎么转移呢, 大多数 题解上说:“1. 新加入一个大小为 sq+1 的数,2. 将之前 i 个数每个都加1”、“可以通过两种操作从 i - 1 的序列得到,即新加一个 √n + 1,或整体 + 1”、……(很明显第二对引号中是错的,因为整体加 1 后 i 不变,所以说网上题解真实性堪忧啊,唉)
大概的意思是 “ ”,怎么理解呢?
咱们得从 dp[i][j] 定义说起
dp[i][j] 其实呢,说它是一个状态,不过是许多最基本的状态的状态集,每个最基本的状态就是选了哪些物品的物品集(可重集),比如 dp[2][20] 就含有 {10,10}、{9,11}等状态
而 dp[i][j] 的状态转移,实际上是把一类的所有最基本状态,刚好地全部转移为另一类状态,
让我们看看这个状态转移是不是这样的:
首先初始状态是什么都没有装:(empty)
然后任何一个目标状态排个序:sq+a1、sq+a2、sq+a3 ……
可以证明,初始状态可以通过一条唯一路径转移到任意合法的目标状态,只用把状态反向转移回去:
- 如果数集(也就是此题的物品集)中最小的数是 sq+1 (a1 == 1),则把它删了,物品数量 -1,再判断一次
- 否则,把所有数 -1 ,最小的数就变为 sq+a1-1,重复这个过程,最小的数迟早变成 sq+1,然后进行转移 1
于是,物品数量会持续减,直到减为零,反向转移到初始状态!(唔,真的巧妙)
知道怎么转移后,便可做了,j 从前往后循环,然后把所有 j 相同的 dp[i][j] 加到 f[j] 里,再跑下面的背包
[1 , sq]的处理
按照背包的思路,可以这样转移(压掉第一维 i )
然后用多重背包的常规优化,用倍增减小每种物品的量,
但是 过不了
题解上说是前缀和优化,
我就纳闷了,前缀和不是每次sum[ j ] += sum[ j - 1 ]?可是这题是隔了一段 i 的!
难不成你每次sum[ j ] += sum[ j - i ]? i 一变化你还得重新跑!唉,离谱……
……
(好像真没问题)
i 只会变化 sq 次(显而易见),每次把[1,n]算一个隔段的前缀和,同上,然后O(1)转移
呜呼!然后输出 f[n] 就完了!
CODE
详见代码吧,不是很长(也就头有点长而已)
#include<map>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) ((-x)&(x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s=getchar();}
return f*x;
}
const int MOD = 23333333;
int n,m,i,j,s,o,k;
int dp[325][MAXN],sq;
int f[MAXN],sum[MAXN];
int main() {
n = read();
sq = (int)sqrt((DB)n);
dp[0][0] = 1;
f[0] = 1;
for(int i = 1;i <= sq;i ++) {
for(int j = (sq+1)*i;j <= n;j ++) {
dp[i][j] = (dp[i-1][j-sq-1] + dp[i][j-i]) % MOD;
(f[j] += dp[i][j]) %= MOD;
}
}
for(int i = 1;i <= sq;i ++) {
for(int j = 0;j <= n;j ++) {
sum[j] = f[j];
if(j >= i) (sum[j] += sum[j-i]) %= MOD;
}
for(int j = i;j <= n;j ++) {//转移每次就加 sum[j-i]( - sum[j-i*i-i])
int pre = j - i*i - i; //注意必须要是关于 i 同余的相减
int k = (pre < 0 ? 0:sum[pre]);
(f[j] += ((LL)sum[j-i] +MOD- k) % MOD) %= MOD;
}
}
printf("%d\n",f[n]);
return 0;
}
精髓
这道题的精髓就在于,dp[i][j] 的设计,你得找到合适的归类方法,同时满足状态数足够少,转移复杂度足够小,这对于每道题是有很高的自由度的
这也许就是DP题的魅力所在吧
LOJ#6089 小 Y 的背包计数问题 - DP精题的更多相关文章
- LOJ #6089. 小 Y 的背包计数问题
LOJ #6089. 小 Y 的背包计数问题 神仙题啊orz. 首先把数分成\(<=\sqrt n\)的和\(>\sqrt n\)的两部分. \(>\sqrt n\)的部分因为最多选 ...
- LOJ 6089 小Y的背包计数问题 —— 前缀和优化DP
题目:https://loj.ac/problem/6089 对于 i <= √n ,设 f[i][j] 表示前 i 种,体积为 j 的方案数,那么 f[i][j] = ∑(1 <= k ...
- loj 6089 小 Y 的背包计数问题——分类进行的背包
题目:https://loj.ac/problem/6089 直接多重背包,加上分剩余类的前缀和还是n^2的. 但可发现当体积>sqrt(n)时,个数的限制形同虚设,且最多有sqrt(n)个物品 ...
- 【LOJ6089】小Y的背包计数问题(动态规划)
[LOJ6089]小Y的背包计数问题(动态规划) 题面 LOJ 题解 神仙题啊. 我们分开考虑不同的物品,按照编号与\(\sqrt n\)的关系分类. 第一类:\(i\le \sqrt n\) 即需要 ...
- [loj6089]小Y的背包计数问题
https://www.zybuluo.com/ysner/note/1285358 题面 小\(Y\)有一个大小为\(n\)的背包,并且小\(Y\)有\(n\)种物品. 对于第\(i\)种物品,共有 ...
- loj6089 小 Y 的背包计数问题
link 吐槽: 好吧开学了果然忙得要死……不过为了证明我的blog还没有凉,还是跑来更一波水题 题意: 有n种物品,第i种体积为i,问装满一个大小为n的背包有多少种方案? $n\leq 10^5.$ ...
- LOJ6089 小Y的背包计数问题(根号优化背包)
Solutioon 这道题利用根号分治可以把复杂度降到n根号n级别. 我们发现当物品体积大与根号n时,就是一个完全背包,换句话说就是没有了个数限制. 进一步我们发现,这个背包最多只能放根号n个物品. ...
- LOJ6089 小Y的背包计数问题 背包、根号分治
题目传送门 题意:给出$N$表示背包容量,且会给出$N$种物品,第$i$个物品大小为$i$,数量也为$i$,求装满这个背包的方案数,对$23333333$取模.$N \leq 10^5$ $23333 ...
- LOJ6089 小Y的背包计数问题 背包
正解:背包 解题报告: 先放传送门! 好烦昂感觉真的欠下一堆,,,高级数据结构知识点什么的都不会,基础又麻油打扎实NOIp前的题单什么的都还麻油刷完,,,就很难过,,,哭辣QAQ 不说辣看这题QwQ! ...
随机推荐
- fpm工具安装
概述 最近在对机房的编译环境做整理,过程曲折而痛苦,记录一下. 之前的一个老项目,在打包的时候用到了一个叫做fpm的工具. 编译环境涉及centos6和centos7,在新的编译环境的过程中,如何安装 ...
- springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑)
springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑) 写在前面: 富文本编辑器,Multi-function Text Editor, 简称 MTE, 是一 ...
- 我的 Java 学习&面试网站又又又升级了!
晚上好,我是 Guide. 距离上次介绍 JavaGuide 新版在线阅读网站已经过去 7 个多月了(相关阅读:官宣!我升级了!!!),这 7 个多月里不论是 JavaGuide 的内容,还是 Jav ...
- BUUCTF-秘密文件
秘密文件 根据提示得知是属于文件被下载了,查看了下流量包直接过滤ftp包 这里看到有个RAR包存在,应该是隐写了 使用foremost分离即可 得到压缩包存在密码 默认四位纯数字爆破即可 flag{d ...
- 揭秘GaussDB(for Redis):全面对比Codis
摘要:Codis集群在国内Redis生态圈很流行,社区已停止维护.本文从架构和特性两方面对比,带你感受华为云GaussDB(for Redis)的全新价值. 本文分享自华为云社区<华为云Gaus ...
- RPA纳税申报机器人
1.机器人开始工作 2.机器人打开企业内部税务平台,自动下载报税底表 3.机器人自动登录地方税务局,填写报税数据 手工报税10分钟/3个表 VS 机器人报税时间2分钟/3个表 处理时间缩短80% 报税 ...
- sql server 跨IP库更新表字段(OPENDATASOURCE 、update)
--跨ip库更新表字段 update uat set goodsType=dev.goodsType from OPENDATASOURCE('SQLOLEDB','Data Source=127.0 ...
- 《Ranked List Loss for Deep Metric Learning》CVPR 2019
Motivation: 深度度量学习的目标是学习一个嵌入空间来从数据点中捕捉语义信息.现有的成对或者三元组方法随着模型迭代过程会出现大量的平凡组导致收敛缓慢.针对这个问题,一些基于排序结构的损失取得了 ...
- mysql实现两个字段合并成一个字段查询
[需求]实现国际化I18N语言切换功能,例如菜单列表.字典等. 主要是个辅助表进行管理语言的配置: 单个字段很简单,直接通过字典配置的数据标签(key)- 表名, 数据键值(value)-表字段名[默 ...
- 剑指offer——day-1
今天开始记录一下剑指offer的题目训练,提升一下自己的编程能力吧 题目一: 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列 ...