小记:多重背包的nV实现
请读者在观看时已经对普通背包有所了解,该文如有错误欢迎指出。
首先,引入一个多重背包经典问题,有 \(n\) 个物品,每个物品有 \(a_i\) 个,每一个的重量为 \(w_i\),价值为 \(v_i\)。
然后求当背包为 \(W\) 时的最大值是多少,我们一个显然的转移是 \(j\) 从大往小枚举,然后 \(f_j = \max\left(f_j,f_{j-z*w_i}+z\times v_i\right)\left(j \ge z\times w_i,1 \le z \le a_i\right)\)。
然后我们考虑把 \(f\) 按 \(j\) 除以 \(w_i\) 的余数分类。
然后对于一组 \(f\),我们考虑用单调队列维护,还是从大到小枚举 \(j\),加下来默认枚举的都在合法范围,先加入 \(f_{j-z*w_i}+z\times v_i\) 这些点,然后弹出一些不合法的,然后 \(j-=w_i\),之后考虑在加入 \(f_{j-a_i*w_i}+a_i\times v_i\) (如果可行的话),注意前面的对头如果大于 \(j\),那么踢出,前面的距离现在的都变近了 \(w_i\),贡献都要减去 \(v_i\),具体可以记一个 \(sum\) 表示减去的,新加的点额外加上 \(sum\) 即可。
由于减是全局减,不影响相对大小,所以合理。
代码实现,emm,有时间在写,不过下面这道例题会有代码的。
要做整个题并不是只有多重背包哦,不过可以看里面的多重背包部分。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
template<typename T>
void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
template<typename T,typename... Args>
void read(T &_x,Args&...others){Read(_x);Read(others...);}
const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
#define pc(x) buf[plen++]=x
#define flush(); fwrite(buf,1,plen,stdout),plen=0;
template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 3e5+10,M = 1010,V = 1000;
int n,q,b[M][M],f[M],ans[N],x,y,sum,h,e,c[M],d[M];
struct w
{
int s,v,w;//个数价值重量
}a[M];
vector<int>v[M],v1[M];
void solve(int l,int r)
{
if(l == r)
{
for(int j = 0;j < v[l].size();j++)
ans[v1[l][j]] = f[v[l][j]];
return;
}
int mid = ((l+r)>>1);
for(int i = 0;i <= V;i++) b[l][i] = f[i];
for(int i = l;i <= mid;i++)
{
for(int j = V;j > V-a[i].w;j--)//多重背包部分
{
h = 1,e = 0; sum = 0;
for(int z = 1;z < a[i].s && j-z*a[i].w >= 0;z++)
{
while(e >= h && f[j-z*a[i].w]+z*a[i].v >= c[e]) e--;
c[++e] = f[j-z*a[i].w]+z*a[i].v,d[e] = j-z*a[i].w;
} sum = 0;
for(int z = j;z >= 0;z -= a[i].w)
{
if(z >= a[i].s*a[i].w)
{
while(e >= h && f[z-a[i].s*a[i].w]+a[i].s*a[i].v+sum >= c[e]) e--;
c[++e] = f[z-a[i].s*a[i].w]+a[i].s*a[i].v+sum,d[e] = z-a[i].s*a[i].w;
}
while(h <= e && d[h] > z) h++;
if(h <= e) f[z] = max(f[z],c[h]-sum);
sum += a[i].v;
}
}
}
solve(mid+1,r);
for(int i = 0;i <= V;i++) f[i] = b[l][i];
for(int i = 0;i <= V;i++) b[r][i] = f[i];
for(int i = mid+1;i <= r;i++)
{
for(int j = V;j > V-a[i].w;j--)
{
h = 1,e = 0;
for(int z = 1;z < a[i].s && j-z*a[i].w >= 0;z++)
{
while(e >= h && f[j-z*a[i].w]+z*a[i].v >= c[e]) e--;
c[++e] = f[j-z*a[i].w]+z*a[i].v,d[e] = j-z*a[i].w;
} sum = 0;
for(int z = j;z >= 0;z -= a[i].w)
{
if(z >= a[i].s*a[i].w)
{
while(e >= h && f[z-a[i].s*a[i].w]+a[i].s*a[i].v+sum >= c[e]) e--;
c[++e] = f[z-a[i].s*a[i].w]+a[i].s*a[i].v+sum,d[e] = z-a[i].s*a[i].w;
}
while(h <= e && d[h] > z) h++;
if(h <= e) f[z] = max(f[z],c[h]-sum);
sum += a[i].v;
}
}
}
solve(l,mid);
for(int i = 0;i <= V;i++) f[i] = b[r][i];
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n);
for(int i = 1;i <= n;i++) read(a[i].w),read(a[i].v),read(a[i].s);
read(q);
for(int i = 1;i <= q;i++) read(x),read(y),x++,v[x].push_back(y),v1[x].push_back(i);
solve(1,n);
for(int i = 1;i <= q;i++) print(ans[i]),pc('\n');
flush();
return 0;
}
/*
注意到在极限数据下e = V是最慢的
所以不妨每次就全跑了
每次会有一个点不能选,我们考虑对于每个点求出如果不选它那么F数组长什么样子
让我看看,每加入一个数二进制优化是Vlog,那么暴力是n^2Vlog的
考虑我们暴力删除还是太浪费了,考虑一个分治的形式
我们希望在solve(l,r)时除了(l,r)每个商品都可以被选择
转移也比较好转移,每次求出mid,求出左边的递归右边,删掉
然后同理另一半,然后删掉回去
复杂度?一共log层复杂度Vnlog^2,足矣通过
有O(nm)的单调队列多重背包做法,Vnlog
原来这是我之前做的那个CF题(
*/
upd:增加一道模版题,不过实现优秀的拆分应该也能过:
注意到是可行性,不需要开数组直接存一个数即可。
#include<bits/stdc++.h>
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
template<typename T>
void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
template<typename T,typename... Args>
void read(T &_x,Args&...others){Read(_x);Read(others...);}
const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
#define pc(x) buf[plen++]=x
#define flush(); fwrite(buf,1,plen,stdout),plen=0;
template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 1e5+10;
int n,m,a[N],c[N],x,y,cnt,k,l,ans;
bool f[N];
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
while(1)
{
read(n),read(m); ans = 0;
if(n == 0 && m == 0) break;
for(int i = 0;i <= m;i++) f[i] = 0; f[0] = 1;
for(int i = 1;i <= n;i++) read(a[i]);
for(int i = 1;i <= n;i++) read(c[i]);
for(int i = 1;i <= n;i++)
{
k = a[i]*c[i];
for(int j = 0;j < a[i];j++)
{
cnt = 0; x = -1; if(f[j]) x = j;//注意到是可行性dp,所以只需要存一个就好了
for(int z = j+a[i];z <= m;z += a[i])
{
if(x != -1 && z-x > k) x = -1;
if(f[z]) x = z;
if(x != -1) f[z] |= 1;
}
}
}
for(int i = 1;i <= m;i++) ans += f[i];
print(ans),pc('\n');
}
flush();
return 0;
}
浙公网安备 33010602011771号