【YbtOJ#473】01背包
题目
题目链接:https://www.ybtoj.com.cn/contest/121/problem/1
给定 \(n\) 个物品,分别求背包容量为 \(1\)、为 \(2\)、……、为 \(m\) 时能获得的最大价值(每一件物品只能取一次,不同的背包容量相互之间均为独立的问题)。
\(n\leq 10^6,m\leq 5\times 10^4,w\leq 300,v\leq 10^9\)。\(w,v\) 分别是体积和价值。
思路
显然把体积相同的放到一起排序之后 dp。假设当前选择到的物品体积为 \(j\),记 \(sum_i\) 表示体积为 \(j\) 的物品前 \(i\) 大价值之和。
对于 \(\{i|i\bmod j=k\}(k\in[0,j))\) 的集合显然可以分开来做。不难证明我们决策是有单调性的(\(sum\) 做一次差分之后显然是递减的,类似四边形不等式证明)。可以考虑单调队列。
对于队首的两个元素,因为具有决策单调性,我们可以二分出第一个位置使得前面的贡献小于后面的贡献。如果这个位置在当前位置前面,那么队首就不会再做任何贡献了。直接弹出。
考虑插入当前这个点,如果队列末尾两个元素的贡献大小交换位置后于最后一个元素和当前插入元素的贡献大小交换位置,那么最后一个元素肯定是不能造成任何贡献的。因为假设它会造成贡献,那么必须再与末尾第二个元素交换之后。但是在此之前插入的元素已经交换到前面了。
显然二分次数是 \(O(mw)\) 的。时间复杂度 \(O(mw\log m)\)。
代码
第一次写四边形不等式 /kel。没有标调自闭了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,M=310;
int n,m,q[N];
ll sum[N],f[2][N];
vector<int> v[M];
bool cmp(int x,int y)
{
return x>y;
}
int binary(int k,int i,int j)
{
int l=1,r=m,mid,id=k&1,len=(j-i)/k;
while (l<=r)
{
mid=(l+r)>>1;
if (f[id^1][i]+sum[mid]-((mid-len>=0)?sum[mid-len]:0)>f[id^1][j]) l=mid+1;
else r=mid-1;
}
return l-1;
}
int main()
{
freopen("jewelry.in","r",stdin);
freopen("jewelry.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=300;i++) v[i].push_back(0);
for (int i=1,x,y;i<=n;i++)
{
scanf("%d%d",&x,&y);
v[x].push_back(y);
}
for (int i=1;i<=300;i++)
sort(v[i].begin()+1,v[i].end(),cmp);
memset(f[0],0xcf,sizeof(f[0]));
f[0][0]=0;
for (int j=1;j<=300;j++)
{
int id=(j&1);
for (int i=1;i<v[j].size();i++) sum[i]=sum[i-1]+v[j][i];
for (int i=v[j].size();i<=m;i++) sum[i]=sum[i-1];
memcpy(f[id],f[id^1],sizeof(f[id]));
for (int k=0;k<j;k++)
{
int hd=1,tl=0;
for (int i=0;i*j+k<=m;i++)
{
while (hd<tl && q[hd]+binary(j,q[hd]*j+k,q[hd+1]*j+k)<i) hd++;
if (hd<=tl) f[id][i*j+k]=max(f[id][i*j+k],f[id^1][q[hd]*j+k]+sum[i-q[hd]]);
while (hd<tl && q[tl-1]+binary(j,q[tl-1]*j+k,q[tl]*j+k)>=q[tl]+binary(j,q[tl]*j+k,i*j+k)) tl--;
q[++tl]=i;
}
}
}
for (int i=1;i<=m;i++)
{
f[0][i]=max(f[0][i],f[0][i-1]);
printf("%lld ",f[0][i]);
}
return 0;
}

浙公网安备 33010602011771号