题面

题解

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

    目录 Java编码安全 数据校验 规则1.1:校验跨信任边界传递的不可信数据 规则1.2:禁止直接使用不可信数据来拼接SQL语句 规则1.4:禁止直接使用不可信数据来记录数据 规则1.6:验证路径前将 ...

  2. .NET C#基础(6):命名空间 - 有名字的作用域

    0. 文章目的   面向C#新学者,介绍命名空间(namespace)的概念以及C#中的命名空间的相关内容. 1. 阅读基础   理解C与C#语言的基础语法.   理解作用域概念. 2. 名称冲突与命 ...

  3. 使用PowerShell下载文件

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月12日. 使用Invoke-WebRequest指令下载文件 [Net.ServicePointManager]::Securit ...

  4. Elasticsearch学习系列一(部署和配置IK分词器)

    Elasticsearch简介 Elasticsearch是什么? Elaticsearch简称为ES,是一个开源的可扩展的分布式的全文检索引擎,它可以近乎实时的存储.检索数据.本身扩展性很好,可扩展 ...

  5. GitLab、Jenkins结合构建持续集成(CI)环境

    1 持续集成 概述及运行流程 1.1 持续集成概述 持续集成概述:持续集成(Continuous integration)持续集成是指开发者在代码的开发过程中,可以频繁的将代码部署集成到主干,并迚程自 ...

  6. rhel安装程序

    Linux下软件分类     rpm软件包,包管理器 yum     deb软件包,包管理器 apt     源代码软件包            一般为".tar.gz".&quo ...

  7. 6 分钟看完 BGP 协议。

    上一篇文章见 万字长文爆肝路由协议! 上面我们聊 RIP .OSPF 协议都是基于 AS 即自治系统内的协议,可以把它们认为是域内路由协议:而下面我们要聊的就是 AS 之间的协议了,这也叫做域间路由协 ...

  8. CMU15445 (Fall 2019) 之 Project#2 - Hash Table 详解

    前言 该实验要求实现一个基于线性探测法的哈希表,但是与直接放在内存中的哈希表不同的是,该实验假设哈希表非常大,无法整个放入内存中,因此需要将哈希表进行分割,将多个键值对放在一个 Page 中,然后搭配 ...

  9. IO流思维导图

    IO思维导图总结 总览: 1.文件 <目标:File类的创建和删除的方法 > public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时, 创建一个 ...

  10. 【机器学习基础】——另一个视角解释SVM

    SVM的另一种解释 前面已经较为详细地对SVM进行了推导,前面有提到SVM可以利用梯度下降来进行求解,但并未进行详细的解释,本节主要从另一个视角对SVM进行解释,首先先回顾之前有关SVM的有关内容,然 ...