标签:先来 pre 限制 如何 tps 数据 复杂度 不能 目标
前置知识:
这篇文章我们主要研究 单调队列优化 \(\text{dp}\) 如何用于背包问题。
\(n\) 个物品,背包体积为 \(V\),每个物品有 \(v_i\)(价值)和 \(w_i\)(重量),每个物品只有 \(1\) 个。求在不超过背包体积的情况下能获得的最大价值。
\(n,V,v_i,w_i \leq 1 \times 10^3\),时间限制 \(1s\),空间限制 \(16MB\).
简单的考虑,用 \(f_{i,j}\) 表示前 \(i\) 个物品,背包体积为 \(j\) 的答案,则易得:
这样可以在 \(\mathcal{O}(nV)\) 的时间内解决问题。
但是你发现空间只有 \(16 \text{MB}\),由于 \(i\) 的决策只取决于上一行同一列,所以可以直接降维,得:
注意 \(j \geq w_i\) 的隐约限制。
伪代码如下:
for (i=1;i<=n;i++)
for (j=V;j>=w[i];j--)
f[j] = max(f[j] , f[j-w[i]] + v[i]);
\(\mathcal{O}(nV)\) 是最优的算法。今天我们所要实现的,仅仅是,把所有的背包算法复杂度都降为 \(\mathcal{O}(nV)\).
\(n\) 个物品,背包体积为 \(V\),每个物品有 \(v_i\)(价值)和 \(w_i\)(重量),每个物品有无限个。求在不超过背包体积的情况下能获得的最大价值。
\(n,V,v_i,w_i \leq 1 \times 10^3\),时间限制 \(1s\),空间限制 \(16MB\).
这样你会发现一个问题,你不知道有多少个!
当然你可以用 \(\lfloor \frac{V}{v_i} \rfloor\) 来表示数量,考虑枚举。
同样的 \(f_{i,j}\),我们可以知道:
大前提是 \(j \geq k \times w_i\),这样转移的复杂度是 \(\mathcal{O}(nVw_i)\),降维之后应该可以通过。
考虑一个简单的加强:
\(n,V,v_i,w_i \leq 1 \times 10^4\),空间限制 \(128MB\).
我们将在下面的研究中,解决这个问题。
\(n\) 个物品,背包体积为 \(V\),每个物品有 \(v_i\)(价值)和 \(w_i\)(重量),每个物品有 \(\text{num}_i\) 个。求在不超过背包体积的情况下能获得的最大价值。
\(n,V,v_i,w_i \leq 1 \times 10^4\),时间限制 \(1s\),空间限制 \(128MB\).
显然我们按照完全背包的方法就可以了,但是 \(\mathcal{O}(nVw_i)\) 是不可能通过 \(10^4\) 的!所以我们需要考虑,如何优化这个问题。
实际上不用单调队列也可以优化,我们先来讲著名的背包优化的几个方法。
二进制!这是个熟悉的东西。
实际上我们背包的瓶颈在于两个:背包物品过多,背包容积过大,背包物品数量过多。
容积过大可能会想到离散,但是这不是排序之类只用知道大小的问题啊!容积是不能突破的,考虑优化物品个数。物品个数怎么可能优化呢?数量?
那你可能会说,这和二进制又有什么关系?
下面我们来说一说吧。
用集合 \(s\) 表示二的幂次集,则 \(s = \{ 2^0 , 2^1 \cdots 2^{\infty}\}\).每个正整数都可以用若干个 \(s\) 集合内的元素相加而成。就是说每个数一定能被分解成若干二的幂次的数之和。
\(10 = 2 +8\)
\(100 = 64 + 32 +4\)
\(1000 = 512 + 256 + 128 + 64 + 32 + 8\)
如何证明?
显而易见,每个数都能被二进制表示。比方说 \(10_{10} = 1001_2\).
那么,用计数原则,\(1001_2 = 1 \times 2^0 + 0 \times 2^1 + 0 \times 2^2 + 1 \times 2^3\)
这样就证明结束了,你没有发现吗?
将一个 \(\{ v_i , w_i , num_i\}\) 的物品,通过 \(num_i\) 进行拆分。
本来假设一个物品有 \(7\) 个,你可以把它拆开成 \(1,2,4\) 个,对这三个物品进行 \(01\) 背包,你就可以得到 \(1 - 7\) 所有可能的答案。最后你把不取的答案单独做一遍就可以了。
原理就是, \(n\) 最多被拆成 \(\log n\) 个二的幂次的数之和,这样保证了时间复杂度是 \(\mathcal{O}(nV \log w_i)\) 的。
for(i=1;i<=n;i++) {
read(x) , read(y) , read(z);
for(j=1;j<=z;j<<=1) v[++cnt]=x*j,w[cnt]=y*j,z-=j;
if(z) v[++cnt]=x*z,w[cnt]=y*z;
}
// use array v and w to do 01 backpack
显然,\(10^4\) 的数据可以把二进制拆分卡死。我们需要严格去掉 \(\log\).
再看一眼这个状态转移:
你会发现,这不是一段连续的决策!这是断续的。
但是,你会发现这样一个问题。
\(f_{i-1 , j} , f_{i-1 , j - w_i} , f_{i-1 , j - 2 \times w_i} \cdots f_{i-1 , j - \text{num}_i \times w_i}\),这是所有 \(f_{i,j}\) 的决策点。
你会发现,这些决策点是同行等差列的,每两个决策点隔着一个 \(w_i\). 这非常好!
你会发现,你可以把 \(V\) 按照模 \(w_i\) 进行分类,余数相同的肯定是一类的。
那样我们就可以把这一段当成连续的决策去做,因为实际上枚举余数 \(\text{mo}\) 和当前周期编号 \(k\). 因为 \(k\) 是连续的,那么 \(\text{mo} + k \times w_i\) 实际上就是当前的决策点。这样我们构造出了若干段连续的决策。
对 \(n\) 个物品每次做一次,所以 \(\mathcal{O}(n)\).
然后同时枚举余数和 \(k\),因为 \(\text{mo} \times k \leq V\),所以本质上这两重循环的 \(\sum\) 不会超过 \(V\),是 \(\mathcal{O}(V)\).
按照单调队列每个节点进出一次的原理,\(\mathcal{O}(nV)\) 的目标已然达成。
for(i=1;i<=n;i++) {
read(v) , read(w) , read(num);
num=min(num,V/v);
for(mo=0;mo<v;mo++) {
l=r=0;
for(k=0;k<=(V-mo)/v;k++) {
x=k,y=f[k*v+mo]-k*w;
while(l<r && q[l].pos<k-num) l++;
while(l<r && q[r-1].val<=y) r--;
q[r].val=y,q[r++].pos=x;
f[k*v+mo]=q[l].val+k*w;
}
}
}
这里我们发现,直接把 \(\lfloor \frac{V}{v_i} \rfloor\) 作为 \(\text{num}\) 就可以直接通过。这样我们实现了完全背包和多重背包的 \(\mathcal{O}(nV)\).
这里博主咕咕咕了,因为时间太少了。
标签:先来 pre 限制 如何 tps 数据 复杂度 不能 目标
原文地址:https://www.cnblogs.com/bifanwen/p/13290049.html