题面

题解

(本篇文章深度剖析,若想尽快做出题的看官可以参考知名博主某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 ……

可以证明,初始状态可以通过一条唯一路径转移到任意合法的目标状态,只用把状态反向转移回去:

  1. 如果数集(也就是此题的物品集)中最小的数是 sq+1 (a1 == 1),则把它删了,物品数量 -1,再判断一次
  2. 否则,把所有数 -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精题的更多相关文章

  1. LOJ #6089. 小 Y 的背包计数问题

    LOJ #6089. 小 Y 的背包计数问题 神仙题啊orz. 首先把数分成\(<=\sqrt n\)的和\(>\sqrt n\)的两部分. \(>\sqrt n\)的部分因为最多选 ...

  2. LOJ 6089 小Y的背包计数问题 —— 前缀和优化DP

    题目:https://loj.ac/problem/6089 对于 i <= √n ,设 f[i][j] 表示前 i 种,体积为 j 的方案数,那么 f[i][j] = ∑(1 <= k ...

  3. loj 6089 小 Y 的背包计数问题——分类进行的背包

    题目:https://loj.ac/problem/6089 直接多重背包,加上分剩余类的前缀和还是n^2的. 但可发现当体积>sqrt(n)时,个数的限制形同虚设,且最多有sqrt(n)个物品 ...

  4. 【LOJ6089】小Y的背包计数问题(动态规划)

    [LOJ6089]小Y的背包计数问题(动态规划) 题面 LOJ 题解 神仙题啊. 我们分开考虑不同的物品,按照编号与\(\sqrt n\)的关系分类. 第一类:\(i\le \sqrt n\) 即需要 ...

  5. [loj6089]小Y的背包计数问题

    https://www.zybuluo.com/ysner/note/1285358 题面 小\(Y\)有一个大小为\(n\)的背包,并且小\(Y\)有\(n\)种物品. 对于第\(i\)种物品,共有 ...

  6. loj6089 小 Y 的背包计数问题

    link 吐槽: 好吧开学了果然忙得要死……不过为了证明我的blog还没有凉,还是跑来更一波水题 题意: 有n种物品,第i种体积为i,问装满一个大小为n的背包有多少种方案? $n\leq 10^5.$ ...

  7. LOJ6089 小Y的背包计数问题(根号优化背包)

    Solutioon 这道题利用根号分治可以把复杂度降到n根号n级别. 我们发现当物品体积大与根号n时,就是一个完全背包,换句话说就是没有了个数限制. 进一步我们发现,这个背包最多只能放根号n个物品. ...

  8. LOJ6089 小Y的背包计数问题 背包、根号分治

    题目传送门 题意:给出$N$表示背包容量,且会给出$N$种物品,第$i$个物品大小为$i$,数量也为$i$,求装满这个背包的方案数,对$23333333$取模.$N \leq 10^5$ $23333 ...

  9. LOJ6089 小Y的背包计数问题 背包

    正解:背包 解题报告: 先放传送门! 好烦昂感觉真的欠下一堆,,,高级数据结构知识点什么的都不会,基础又麻油打扎实NOIp前的题单什么的都还麻油刷完,,,就很难过,,,哭辣QAQ 不说辣看这题QwQ! ...

随机推荐

  1. 48. ResNet为什么能训练出1000层的模型

    先回顾一下resnet怎么处理它的梯度消失,使得能处理训练1000层:

  2. JAVA 异常 基本知识

    异常 异常定义 异常是运行过程中出现的错误 人为错误:填写错误等 随机错误:网络中断.内存耗尽等 一个健壮的程序必须处理各种各样的错误 Java的异常是class Object Throwable E ...

  3. tmux(Terminal MultipleXer)命令使用

    作用:命令行多窗口显示:命令行程序与本机脱离 1 安装tmux (1)redhat.centos系统 yum install tmux (2)ubuntu系统 apt-get install tmux ...

  4. 根据数据中的key获取value值

    一.测试数据准备 List<Map<String, String>> result = new ArrayList();Map<String, String> ma ...

  5. BUUCTF-刷新过的图片

    刷新过的图片 刷新在MISC中比较特殊,一般是指F5隐写方式 直接使用工具提取出来,发现生成的是Pk开头的,应该是zip格式 使用16进制确认了是ZIP,将生成的output.txt改为output. ...

  6. WPF开发随笔收录-心电图曲线绘制

    一.前言 项目中之前涉及到胎儿心率图曲线的绘制,最近项目中还需要添加心电曲线和血样曲线的绘制功能.今天就来分享一下心电曲线的绘制方式: 二.正文 1.胎儿心率曲线的绘制是通过DrawingVisual ...

  7. Mysql中的小技巧

    1.where 字段名 regexp '正则表达式' 正则符号: ^ $ . [ ] * | . 表示1个任意字符 * 表示前面重复0次,或者任意次 ^ 开始 $ 结尾 [] 范围 | 或 sql示例 ...

  8. pyflink的安装和测试

    pyflink安装 安装前提:python3.6-3.8 参考:Installation | Apache Flink Python version (3.6, 3.7 or 3.8) is requ ...

  9. 自己动手实现 HashMap(Python字典),彻底系统的学习哈希表(上篇)——不看血亏!!!

    HashMap(Python字典)设计原理与实现(上篇)--哈希表的原理 在此前的四篇长文当中我们已经实现了我们自己的ArrayList和LinkedList,并且分析了ArrayList和Linke ...

  10. Android Studio的初次认识

    Android的初试 一.认识Android Studio 在我们新建项目的时候,会遇到这样的一个窗口,首先我们认识一下这些都是什么,这样我们才能够更好的进行下一步的学习! 这里的 Phone and ...