• 洛谷.1782.旅行商的背包(背包DP 单调队列)


    题目链接(卡常背包)

    朴素的多重背包是: (f[i][j] = max{ f[i-1][j-k*v[i]]+k*w[i] }),复杂度 (O(nV*sum num_i))
    可以发现求(max)时有很多值是被重复枚举过的

    换一种方程表示形式,对于每个(v[i]),设(j=K*v[i]+r,quad K=j/v[i],quad r=j\%v[i]),即按照(\%v[i])的余数分别进行dp(第二层枚举余数(r)
    再枚举(k=0sim K-1)(去掉(k)个物品(i)),那么 (f[i][j] = max{ f[i-1][k*v[i]+r]-k*w[i] } + K*w[i])
    里面这一部分可以用单调队列维护。

    大体是这个样子(这里(f)(f[i])(g)(f[i-1])(k)是指当前的体积为(k imes v_i+r)(r)是枚举的余数,(v_i)是当前物品(i)的单位体积,(num_i)(i)物品个数,(val_i)是物品(i)单位价值):

    [f_k=maxlimits_{k-jleq num_i}{g_{j}+(k-j) imes val_i}\f_k=maxlimits_{k-jleq num_i}{g_{j}-j imes val_i}+k imes val_i ]

    代码就是第一层枚举(i),第二层枚举(r=0sim v_i-1),第三层枚举(k),更新(f[i][k imes v_i+r]),用单调队列优化。

    如果你还没有看懂或想明白怎么用单调队列...
    单调队列里每次存进去一个(f[i-1][j imes v_i+r]-j imes val_i),更新的时候是$$egin{aligned}f[i][k imes v_i+r]&=q[h]+k imes val_i&=max_{k-jleq num_i}{ f[i-1][j imes v_i+r]-j imes val_i }+k imes val_i&=max_{k-jleq num_i}{ f[i-1][j imes v_i+r]+(k-j) imes val_i }end{aligned}$$
    (k-j)实际就是选了多少个物品(i)

    复杂度是 (O(nV))。二三层枚举相当于把(0sim V)都跑了一遍且只有一遍。

    另外二进制拆分也可做,复杂度 (O(nV*sum log(num_i)))。由于数据随机下 (num_i)可能比较小,(sum log(num_i))是期望 (O(n)) 的。
    而且这种方法常数更小,所以比单调队列还快。。

    另一问最大数量只有5,可以直接 (V^2) 暴力。。

    注意当前枚举的体积now与数量的限制: 当队首最优值的体积(用的数量 (k_h))+当前物品最大体积(个数上限(num))<当前枚举的体积((k))时,要弹出队首
    另外最后可以直接输出(f[V])?我觉得不太对啊,大概是那(m)个物品一定会产生正权值?

    卡常是真恶心==

    #include <cstdio>
    #include <cctype>
    #include <algorithm>
    #define gc() getchar()
    const int N=1e4+5;
    
    int n,m,V,f[N],q[N],nm[N];
    
    inline int read()
    {
    	int now=0,f=1;register char c=gc();
    	for(;!isdigit(c);c=gc()) if(c=='-') f=-1;
    	for(;isdigit(c);now=now*10+c-'0',c=gc());
    	return now*f;
    }
    
    int main()
    {
    	n=read(),m=read(),V=read();
    	for(int v,w,num,now,tmp,i=1; i<=n; ++i)
    	{
    		v=read(),w=read(),num=read();
    		for(int j=0; j<v; ++j)//r
    			for(int k=0,h=1,t=0; (now=k*v+j)<=V; ++k)//now:当前体积 
    			{
    				tmp=f[now]-k*w;
    				while(h<=t && q[t]<tmp) --t;
    				nm[++t]=k, q[t]=tmp;
    				if(nm[h]+num<k) ++h;//每次仅会弹出一个元素 
    				f[now]=q[h]+k*w;
    			}
    	}
    	for(int a,b,c,i=1; i<=m; ++i)
    	{
    		a=read(),b=read(),c=read();
    		for(int j=V; j; --j)
    			for(int k=0; k<=j; ++k)
    				f[j]=std::max(f[j],f[j-k]+(a*k+b)*k+c);
    	}
    //	int res=f[1];
    //	for(int i=2; i<=V; ++i) res=std::max(res,f[i]);
    	printf("%d",f[V]);
    
    	return 0;
    }
    
  • 相关阅读:
    如何使得VIM显示行号
    mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication的解决方法
    重启PHP命令
    一个方便的shell命令,查看软件安装目录
    Centos中安装vim
    centos yum安装mysql
    nginx安装php和php-fpm
    大数据实时计算工程师/Hadoop工程师/数据分析师职业路线图
    vim命令
    linux 下MySQL的安装
  • 原文地址:https://www.cnblogs.com/SovietPower/p/8459347.html
Copyright © 2020-2023  润新知