先自我介绍一下,本人大一,计算机专业本科在读,大学0基础自学算法,无信息竞赛基础,实力低微,这是本人第一次写题解来记录自己做题时的感悟,还是比较紧张的QWQ~
由于实力不济,思路上可能会有很多疏漏,希望各路大神不吝赐教!
一、题目描述
二、思路分析
这题是一道比较明显的dp,算法本身是不难确定的,但应如何确立状态函数的两个维度呢?第一想法是用dp[i][j]表示前i个货物在j的代价下能够获取的净收入最大值,但显然行不通,因为在推演过程中无法确定货物对应仓库的编号是否已经使用过,这将是最大的障碍,一时间有点摸不清头脑。
然后我又想到了分组dp再集中处理,但也没有可行思路,反而把问题越想越复杂了。
最后,我转念一想,这题的主角不应该是“货物”,而应该是“仓库”。因为仓库中的货物无论先取哪一个,只要取的数目相同,代价都是一样的,利用这一特性,我们可以弱化“货物”这个概念,就单纯去想每一个仓库要怎么取最优,就好了!所以为了获取最大净收入,我可以直接把货物扔仓库并进行降序排序,这样在每一个仓库中从大到小依次取,找出整体最大值再合并,就是我们要的状态转移了。
三、实现过程和注意事项
1.用一个vector数组a来表示货物价格,a[i][j]表示第i个仓库降序第j的货物的价格;
2.设置状态函数。dp[i][j]表示前i个仓库,以不高于j的费用调度货物,所能达到的净利润最大值;
3.思考状态转移方程。如果不选取第i个仓库,那么dp[i][j]=dp[i-1][j];如果选择k个货物,那么dp[i][j]=dp[i-1][j-b[i]-c[i]*k]+(a[i][l])-b[i]-c[i]*k.最后将所有范围内的这一转移结果取max即可;
4.注意范围:k<=a[i].size()&&j-b[i]-c[i]*k>=0;
5.初始化dp[0][i]=0.
四、总结
对于dp,大部分像我这种初学者比较困惑的一点就是——维度的含义怎么设置,状态转移方程怎么确立,以及如何初始化之类的问题。通过多练习把这些问题搞懂,结合具体问题具体分析,dp题自然就迎刃而解了。
通过这道题,我们可以获得一些启发——dp没有思维定式,不要想当然地认为就应该去转移货物,殊不知这是命题人的高级障眼法罢了。
五、满分代码
#include<bits/stdc++.h>using namespace std;#define ll long long//csp34-4 货物调度int dp[1005][40010];//前i个仓库费用j可以获得的最大总价值vector<int>a[1005];//第i个仓库的第j个货物的价值int b[1005],c[1005];//仓库属性bool dcmp(int a,int b){ return a>b;}int main(){ int n,m,v; cin>>n>>m>>v; for(int i=0;i<=40000;i++)dp[0][i]=0; for(int i=1;i<=n;i++){ cin>>b[i]>>c[i]; } for(int i=1;i<=m;i++){ int val,t; cin>>val>>t; t++; a[t].push_back(val); } for(int i=1;i<=n;i++){ sort(a[i].begin(),a[i].end(),dcmp); } for(int i=1;i<=n;i++){ for(int j=0;j<=40000;j++){ dp[i][j]=dp[i-1][j];//不选i int sum=0; for(int k=0;k<a[i].size();k++){//选i if(b[i]+c[i]*(k+1)>j)break; sum+=a[i][k];//收益和 dp[i][j]=max(dp[i][j],dp[i-1][j-b[i]-c[i]*(k+1)]+sum-b[i]-c[i]*(k+1)); } } } int ans; for(int i=0;i<=40000;i++){ if(dp[n][i]>=v){ ans=i;break; } } cout<<ans; return 0;}
需要说明的一点是,我没有进行降维处理,因为数据范围不是很大,写出来一次就过了也就没进行修改。也不麻烦,只是要注意j从后往前转移即可。
下面是评测记录: