【动态规划】背包dp

青春没有售价,dp速学一下。
参考文章

01背包

在01背包问题中,每个物品只能放一次进背包。
d p [ i ] [ j ] dp[i][j] dp[i][j]:第i个物品,j容量
状态转移公式: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + p r i [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+pri[i]) f[i][j]=max(f[i1][j],f[i1][jw[i]]+pri[i])
其中 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]表示不放该物品, f [ i − 1 ] [ j − w [ i ] ] + p r i [ i ] f[i-1][j-w[i]]+pri[i] f[i1][jw[i]]+pri[i]

空间优化:滚动数组

遍历每列时为了使用到 i − 1 i-1 i1行的数据,注意要从后向前遍历。

为什么从后向前遍历更新:
最小的背包容量开始考虑放物品(即正序遍历),那么在更新较大的背包容量 j 时,较小的背包容量 j-v[i] 可能已经考虑过了物品 i。这会导致物品 i 被错误地计算两次,即它在更新 f[j-v[i]] 时被考虑过一次,在更新 f[j] 时又被考虑。
因为逆序是从大到小考虑,所以,并不会发生上述重复考虑的情况( 内层循环中,逆序一开始时,先考虑最大的容量是否能放入,这样处理,就可以使f[j-v[i]] + w[i]只计算一次,不会重复计算 )
原文链接:https://blog.csdn.net/2301_79558858/article/details/137546255

例题:P1964

map<string,int>a,b,c;
int dp[N],val[N];//把物品都分到一个个小格子里面
//01背包
void solve(){
    int m,n;
    cin>>m>>n;
    m=21-m;
    forr(i,1,n){
        int aa,bb,cc;
        cin>>aa>>bb>>cc;
        string s;cin>>s;
        a[s]+=aa;
        b[s]=bb;
        c[s]=cc;
    }
    int cnt=1;
    for(auto &i:a){
        string s=i.first;
        while (i.second>=c[s])
        {
            val[cnt++]=c[s]*b[s];
            i.second-=c[s];
        }
        if(i.second){
            val[cnt++]=i.second*b[s];
        } 
    }
    forr(i,1,cnt-1){//对每件物品
        reforr(j,1,m){//对背包格子容量
            dp[j]=max(dp[j],dp[j-1]+val[i]);
        }
    }
    cout<<dp[m]<<endl;
}

完全背包

每种物品都有无限件可用。
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − k ∗ w [ i ] ] + k ∗ p r i [ i ] ) , 0 ≤ k ⋅ w [ i ] ≤ j f[i][j]=max(f[i][j],f[i][j-k*w[i]]+k*pri[i]),0\leq k·w[i]\leq j f[i][j]=max(f[i][j],f[i][jkw[i]]+kpri[i])0kw[i]j

小优化

对一些特定数据,减少件数。

  • 去掉容量大于总容量V的物品
  • 若两件物品i、j满足 c [ i ] ≤ c [ j ] , w [ i ] ≥ w [ j ] c[i]\leq c[j],w[i]\geq w[j] c[i]c[j],w[i]w[j],则将物品j去掉,不用考虑。

转化为01背包

  • 容量为V,第i件物品最多选 V w [ i ] V\over w[i] w[i]V
    从前往后比

01背包从后向前遍历,保证每件物品只选一次

scanf("%d %d",&t,&m);
for(int i=0;i<m;i++){
    scanf("%d %d",&w[i],&pri[i]);
    //for(int p=t;p>=0;p--){
        // if(ti[i]<=p){ p>=ti[i]时才操作
    for(int p=w[i];p<=t;p++){//从前往后 跟自己比 能放则放
    		//一维数组优化
            dp[p]=max(dp[p],dp[p-w[i]]+pri[i]);
    }
}

多重背包

每件物品最多有t[i]件可用
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − k ∗ w [ i ] ] + k ∗ p r i [ i ] ) , 0 ≤ k ⋅ w [ i ] ≤ t [ i ] f[i][j]=max(f[i][j],f[i][j-k*w[i]]+k*pri[i]),0\leq k·w[i]\leq t[i] f[i][j]=max(f[i][j],f[i][jkw[i]]+kpri[i])0kw[i]t[i]
所以再加一层循环枚举件数

P1077 采花

int dp[N];
void solve(){
    int n,m;cin>>n>>m;
    vector<int>a(n+1);
    forr(i,1,n){
        cin>>a[i];
    }
    dp[0]=1;
    forr(i,1,n){
        reforr(j,0,m){
            forr(k,1,min(a[i],j)){//三重循环
                dp[j]=(dp[j]+dp[j-k])%MOD;
            }
        }
    }
    cout<<dp[m]<<endl;
}

转化为01背包 二进制优化

  • 分解:跟二进制一样,把件数 t [ i ] t[i] t[i]拆分为 1   2   4   8... 1\ 2\ 4\ 8 ... 1 2 4 8... t [ i ] = ∑ i = 0 k − 1 2 i + ( t [ i ] − 2 k + 1 ) t[i]=\sum_{i=0}^{k-1}2^i+(t[i]-2^k+1) t[i]=i=0k12i+(t[i]2k+1),拆成 l o g 2 t [ i ] log_2t[i] log2t[i]个物品。拆分后的这些物品组合相加,件数在 [ 1 , t [ i ] ] [1,t[i]] [1,t[i]]之间。
  • 01背包处理拆完的物品

P1776 宝物筛选

void solve(){
	int n,W;
	cin>>n>>W;
	forr(i,1,n){
		cin>>v[i]>>w[i]>>m[i];
	}
	//二进制拆分
	int nn=0;
	forr(i,1,n){
		for(int j=1;j<=m[i];j<<=1){//把m[i]个物品分成 1 2 4 8 16...
			m[i]-=j;
			nw[++nn]=j*w[i];
			nv[nn]=j*v[i];
		}
		if(m[i]){//有剩下的
			nw[++nn]=m[i]*w[i];
			nv[nn]=m[i]*v[i];
		}
	}
	//01背包
	vector<int>dp(W+1,0);
	forr(i,1,nn){
		for(int j=W;j>=nw[i];j--){
			dp[j]=max(dp[j],dp[j-nw[i]]+nv[i]);
		}
	}
	cout<<dp[W]<<endl;
}

P2347 砝码称重

直接背包dp
int w[7]={0,1,2,3,5,10,20};
void solve(){
	vector<int>c;
	c.push_back(0);
	int n=0;
	forr(i,1,6){
		int a;cin>>a;
		forr(j,1,a){
			c.push_back(w[i]);//把多重背包变成01背包
			n++;
		}
	}
	int sum=0;
	for(auto i:c)sum+=i;
	vector<int>dp(sum+2,0);//dp[j] 转移到j的方案数
	dp[0]=1;
	forr(i,1,n){
		for(int j=sum;j>=c[i];j--){
			dp[j]+=dp[j-c[i]];
		}
	}
	int ans=0;
	forr(i,1,sum){
		if(dp[i])ans++;
	}
	cout<<"Total="<<ans<<endl;
}
bitset优化
int w[7]={0,1,2,3,5,10,20};
bitset<1010>b;//第i位是1->i是可以枚举到的数
void solve(){
	vector<int>c;
	c.push_back(0);
	int n=0;
	b[0]=1;//初始是一个砝码不用
	forr(i,1,6){
		int a;cin>>a;
		forr(j,1,a){
			b|=(b<<w[i]);//上一个状态+w[i]->下一个状态
		}
	}
	int ans=b.count()-1;//-1因为不包括一个砝码也不用的情况
	//bitset count()位上为1的个数
	cout<<"Total="<<ans<<endl;
}

混合背包

有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)

分组背包

物品被划分为若干组,每组中的物品互相冲突,最多选一件。
f [ k ] [ j ] = m a x ( f [ k − 1 ] [ j ] , f [ k − 1 ] [ j − w [ i ] ] + p [ i ] , 物品 i ∈ 组 k f[k][j]=max(f[k-1][j],f[k-1][j-w[i]]+p[i],物品i \in组k f[k][j]=max(f[k1][j],f[k1][jw[i]]+p[i],物品ik

forr(i,1,n){//组数
	cin>>m;
	forr(j,1,m)cin>>w[j]>>p[j];//组中的物品
	//下与01背包类似
	reforr(k,0,V){//容量
		forr(j,1,m){
			if(k>=w[j])dp[k]=max(dp[k],dp[k-w[j]]+p[j]);
		}
	}
}

你可能感兴趣的:(动态规划,动态规划,算法)