• 洛谷 P3188 [HNOI2007]梦幻岛宝珠(dp)


    传送门


    解题思路

    题意很简单,就是一个有特殊条件的01背包:
    物品的体积很大,并且可以写成 (a imes 2^b) 的形式。
    肯定是从这种特殊限制入手考虑,而且很容易想到按照二进制位分开做。
    我们设 (f[i][j]) 表示对体积表示为 (a imes 2^i) 的物品进行01背包,背包的总体积为 ((j imes2^i)) 时的最大价值。
    再考虑如何合并每一位。
    (dp[i][j]) 表示对体积小于等于 (a imes 2^i) 的物品进行01背包,背包总体积为 ((j imes 2^i+W二进制的后i-1位)) 时的最大价值。
    换种说法就是dp到了第i位时,保证后i-1位满足W的要求。
    因为每一个二进制位为0或者1,所以合并的时候需要考虑低位向高位借位,即第i位每减1,第i-1位就加2。
    预处理一个 (num[i]) 表示体积表示为 (a imes 2^i) 的物品的 (a) 的和。
    转移方程:

    [dp[i][j]=max(dp[i][j],f[i][j-k]+dp[i-1][min(num[i-1],2 imes k+(w>>(i-2)& 1)) ]) ]

    最后的答案很显然就是 (dp[W的最高位][1])
    注意的细节:

    1. <<运算符有限度低于+
    2. 转移方程中的min的原因是有可能转给i-1的2*k太大以至于所有的加起来仍小于2*k,这时候如果用2*k将会数组越界或者返回0(以为没有更新过)
    3. num[i]在求dp的过程中要动态更新(具体看总代码53行),意义改变为体积小于等于 (a imes 2^i) 的物品的和的a值。

    AC代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<iomanip>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    const int maxn=105;
    int n,m,w[maxn],W[maxn],dp[35][maxn],f[35][maxn],v[maxn],to[35][maxn],num[maxn];
    inline int getmax(int x){
    	int res=0;
    	while(x>0){
    		res++;
    		x>>=1;
    	}
    	return res;
    }
    inline int getmin(int x){
    	int res=1;
    	while((x&1)==0){
    		res++;
    		x>>=1;
    	}
    	return res;
    }
    int main(){
    	ios::sync_with_stdio(false);
    	while(1){
    		memset(dp,0,sizeof(dp));
    		memset(f,0,sizeof(f));
    		memset(v,0,sizeof(v));
    		memset(to,0,sizeof(to));
    		memset(num,0,sizeof(num));
    		cin>>n>>m;
    		if(n==-1&&m==-1) break;
    		int maxm=getmax(m);
    		for(int i=1;i<=n;i++){
    			cin>>w[i]>>v[i];
    			int id=getmin(w[i]);
    			to[id][++to[id][0]]=i;
    			W[i]=w[i]/(1<<(id-1));
    			num[id]+=W[i];
    		}
    		for(int i=1;i<=maxm;i++){
    			for(int j=1;j<=to[i][0];j++){
    				for(int k=num[i];k>=0;k--){
    					if(k-W[to[i][j]]<0) break;
    					f[i][k]=max(f[i][k],f[i][k-W[to[i][j]]]+v[to[i][j]]);
    				}
    			}
    		}
    		for(int i=1;i<=maxm;i++){
    			num[i]+=(num[i-1]+1)/2;
    			for(int j=0;j<=num[i];j++){
    				for(int k=0;k<=j;k++){
    					dp[i][j]=max(dp[i][j],f[i][j-k]+dp[i-1][min(num[i-1],2*k+(m>>(i-2)&1))]);
    				}
    			}
    		}
    		cout<<dp[maxm][1]<<endl;
    	}
        return 0;
    }
    
  • 相关阅读:
    有点忙啊
    什么是协程
    HDU 1110 Equipment Box (判断一个大矩形里面能不能放小矩形)
    HDU 1155 Bungee Jumping(物理题,动能公式,弹性势能公式,重力势能公式)
    HDU 1210 Eddy's 洗牌问题(找规律,数学)
    HDU1214 圆桌会议(找规律,数学)
    HDU1215 七夕节(模拟 数学)
    HDU 1216 Assistance Required(暴力打表)
    HDU 1220 Cube(数学,找规律)
    HDU 1221 Rectangle and Circle(判断圆和矩形是不是相交)
  • 原文地址:https://www.cnblogs.com/yinyuqin/p/15228579.html
Copyright © 2020-2023  润新知