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! ...
随机推荐
- 阻碍NB-IoT技术在智能水表发展的4个原因分析
与以往的机械水表不同,根据设备所搭载的模块,智能水表分为IC卡智能表.光电直读智能表以及无线远传智能表.随着物联网技术和工业的发展,无线远程传输智能水表开始被水务公司广泛使用. 以往的机械水表.指针式 ...
- 技术分享 | 一步一步学测试平台开发-Vue restful请求
本文节选自霍格沃兹测试学院内部教材 一般在构建应用时需要访问后端的 API 接口获取后端数据并展示.做这件事的方法有很多种(比如 axios,vue-resource,fetch-jsonp),使用 ...
- WAVE音频格式及及转换代码
音频信号的读写.播放及录音 python已经支持WAV格式的书写,而实时的声音输入输出需要安装pyAudio(http://people.csail.mit.edu/hubert/pyaudio).最 ...
- JavaScript与Node.js一起打造一款聊天App
聊天是我们人与人交流最直接的方式,互联网的加入使我们交流更加便捷.我们手机上的微信.QQ是我们手机必不可少的应用软件.那么,我们是否可以做一款聊天应用呢? 之前我自己闲着没事,研究过一些技术,做了一款 ...
- UiPath参数介绍和使用
一.参数介绍 用于将数据从一个项目传递到另一个项目.在全局意义上,它们类似于变量,因为它们动态地存储数据并传递给它.变量在活动之间传递数据,而参数在自动化之间传递数据.因此,它们使你能够一次又一次地重 ...
- Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)
上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...
- Docker部署jar包运行
1.上传jar包到服务器 2.在该目录下创建Dockerfile 文件 vi Dockerfile 3.然后将下面的内容复制到Dockerfile文件中 FROM java:8 MAINTAINER ...
- 复制docker容器中的nginx某个文件到linux中
前提:docker容器中的nginx要开启
- ShardingSphere-proxy-5.0.0企业级分库分表、读写分离、负载均衡、雪花算法、取模算法整合(八)
一.简要说明 以下配置实现了: 1.分库分表 2.每一个分库的读写分离 3.读库负载均衡算法 4.雪花算法,生成唯一id 5.字段取模 二.配置项 # # Licensed to the Apache ...
- putchar与getchar
#include <stdio.h>#include <stdlib.h>void myputs(char*p) //此处的*号是标志,标志这P是一个指针{ if(p==NUL ...