刷题总结——竞赛得分(ssoj)
题目:
题目描述
ZZH 在经历了无数次学科竞赛的失败以后,得到了一个真理:做一题就要对一题!但是要完全正确地做对一题是要花很多时间(包括调试时间),而竞赛的时间有限。所以开始做题之前最好先认真审题,估计一下每一题如果要完全正确地做出来所需要的时间,然后选择一些有把握的题目先做。 当然,如果做完了预先选择的题目之后还有时间,但是这些时间又不足以完全解决一道题目,应该把其他的题目用贪心之类的算法随便做做,争取“骗”一点分数。根据每一题解题时间的估计值,确定一种做题方案(即哪些题目认真做,哪些题目“骗”分,哪些不做),使能在限定的时间内获得最高的得分。
输入格式
第一行有两个正整数 N 和 T,表示题目的总数以及竞赛的时限(单位秒)。以下的 N 行,每行 4 个正整数 W1i 、T1i 、W2i 、T2i ,分别表示第 i 题:完全正确做出来的得分、完全正确做出来所花费的时间(单位秒)、“骗”来的分数、“骗”分所花费的时间(单位秒)。
其中,3≤N≤30,2≤T≤1080000,1≤W1i ,W2i≤30000; 1≤T1i,T2i≤T。
输出格式
输出所能得到的最高分值。
样例数据 1
样例数据 2
题解:
这里引用ssoi官方题解:
简单动态规划,二维费用背包问题,需要使用滚动数组。
此题几乎是裸的 0/1 背包。而 0/1 背包就是一个很典型的顺序可以交换的例子。当然前提是状态是 f[i][j]代表前 i 道题用 j 的时间得到的最多的分数。这种情况下你把 i 放在外层循环或把 j 放在外层循环都是可以 AC 的。因为状态转移方程是 f[i][j]=max(f[i-1][j-cost1[i]]+score1[i],f[i-1][j-cost2[i]]+score2[i],f[i-1][j])。这样无论循环是哪个在外面,枚举到当前状态时,所需的前驱状态都已经计算出来了。
不过特别的,对于这道题,如果采取这样的状态,空间上有些紧张。所以我们必须把 i 那一维优化掉。注意到i每次是由 i-1 转移过来的,并且前驱状态的 j 一定小于等于当前状态的 j!所以如果不设 i 那一维,把 i 放在外面循环并且 j 循环用 downto 的话,就可以保证每次转移的前驱都是 i-1 的情况。这样时间复杂度不变依然是 O(NT),但空间复杂度降到了 O(T)。这是我们对这道题的特殊之处的特殊优化,也可以说我们是对 0/1 背包的特殊之处的特殊优化。
注意用一位数组优化!注意循环外层先枚举题目,然后注意再开两个dp数组防止枚举到同一道题正解贪心都做的情况!!
连基本的背包dp都做不来了····
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<string> #include<cstring> #include<algorithm> using namespace std; const int N=35; const int W=1080005; int w1[N],v1[N],w2[N],v2[N],n,t; int dp[W],dp1[W],dp2[W]; int main() { //freopen("a.in","r",stdin); scanf("%d%d",&n,&t); for(int i=1;i<=n;i++) scanf("%d%d%d%d",&v1[i],&w1[i],&v2[i],&w2[i]); for(int i=1;i<=n;i++) { for(int j=t;j>=w1[i];j--) dp1[j]=max(dp1[j],dp[j-w1[i]]+v1[i]); for(int j=t;j>=w2[i];j--) dp2[j]=max(dp2[j],dp[j-w2[i]]+v2[i]); for(int j=1;j<=t;j++) dp[j]=max(dp1[j],dp2[j]); } int ans=0; for(int j=1;j<=t;j++) ans=max(ans,dp[j]); cout<<ans<<endl; return 0; }