T 1 : P 2722 \tt T1:P2722 T1:P2722
完全背包板子题,因为一类题可以重复选择,不再过多赘述。
容量为
m
m
m,物品
t
i
t_i
ti,价值
p
i
p_i
pi,答案
d
p
m
dp_m
dpm,注意枚举容量要正序循环。
状态:
d
p
j
dp_j
dpj 表示背包放入容量为
j
j
j 的物品的最大值
状态转移方程:
d
p
j
=
max
(
d
p
j
,
d
p
j
−
t
i
+
p
i
)
dp_j=\max(dp_j,dp_{j-t_i}+p_i)
dpj=max(dpj,dpj−ti+pi)
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[10005],w[10005],val[10005];
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
cin>>val[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=w[i];j<=m;j++)
{
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
cout<<dp[m];
return 0;
}
T 2 : P 1802 \tt T2:P1802 T2:P1802
本题01背包,近乎于一个板子,一个物品要么选,要么不选。
x
x
x 为容量,
u
s
e
i
use_i
usei 为物品,
w
i
n
i
win_i
wini 和
l
o
s
e
i
lose_i
losei 皆为价值。
注意1:本题不仅能打败有经验(物品放得下),且打不败也有经验(物品放不下),所以要特判。
状态转移方程:
- 放得下:
d p j = m a x ( d p j + l o s e i , d p j − u s e i + w i n i ) dp_j=max(dp_j+lose_i,dp_{j-use_i}+win_i) dpj=max(dpj+losei,dpj−usei+wini) - 放不下:
d p j = d p j + l o s e i dp_j=dp_j+lose_i dpj=dpj+losei
注意2:因为最后要
×
5
\times5
×5,记得开long long。
代码:
#include<iostream>
using namespace std;
const int N=1005;
long long n,x,lose[N],win[N],w[N],dp[N];
int main()
{
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>lose[i]>>win[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=x;j>=0;j--)
{
if(j>=w[i])
{
dp[j]=max(dp[j]+lose[i],dp[j-w[i]]+win[i]);
}
else
{
dp[j]=dp[j]+lose[i];
}
}
}
cout<<dp[x]*5;
return 0;
}
T 3 : P 2340 \tt T3:P2340 T3:P2340
基本做法
- 它的容量为情商的和与智商的和,这类题可以见此题。
但不过这样做时间复杂度会上天,假设智商情商全部
1000
1000
1000,有
400
400
400 头奶牛,那么总共容量就为
400
⋅
1000
400\cdot1000
400⋅1000,也就是
40
40
40 万。
三重循环,第
1
1
1 层枚举物品
400
400
400 次,第
2
2
2 层枚举智商
400000
400000
400000 次,情商同理,那么总时间复杂度为
O
(
64
⋅
1
0
12
)
O(64\cdot10^{12})
O(64⋅1012),恐怖如斯。
优化做法
- 我们可以将奶牛的智商视为重量,而情商视为价值(的确,这看起来很别扭)。
- 那样,则状态为: d p j dp_j dpj 表示每个物品的重量不超过 j j j 的做大价值。
- 答案同样麻烦,我们还要枚举到总容量,如果
d
p
i
dp_i
dpi 是个正数,那么就与
a
n
s
ans
ans 取最值:
a n s = max ( a n s , d p i + i ) ans=\max(ans,dp_i+i) ans=max(ans,dpi+i)
细节:
- 下标不可以为 0 0 0,所以要加上一个值。
- 智商负数和整数分开讨论,正数倒序,负数正序。
- 因为每个物品可能有负数,所以将 d p dp dp 数组统一赋值为极小值。(如果用 m e m s e t \tt memset memset 的话,那么就用 − 0 x 3 f \tt -0x3f −0x3f 或 0 x c f \tt 0xcf 0xcf)
- 将
d
p
dp
dp 数组的最后一个值设为
0
0
0,不然都会是个负数。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=8e5+5,d=4e5;
int n,iq[405],eq[405],dp[N],ziq;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>iq[i]>>eq[i];
memset(dp,-0x3f,sizeof dp);
dp[d]=0;
for(int i=1;i<=n;i++){
if(iq[i]>=0)
for(int j=d<<1;j>=iq[i];j--)
dp[j]=max(dp[j],dp[j-iq[i]]+eq[i]);
else
for(int j=0;j<=(d<<1)+iq[i];j++)
dp[j]=max(dp[j],dp[j-iq[i]]+eq[i]);
}
int ans=0;
for(int i=d;i<=d<<1;i++)
if(dp[i]>=0)
ans=max(ans,dp[i]+i-d);
cout<<ans;
return 0;
}
T 4 : P 1853 \tt T4:P1853 T4:P1853
又是一个近乎于完全背包板子的题,考试时以为是
5
5
5 倍经验日的变形,把最后的答案
×
n
\times n
×n 再加上本金
s
s
s 即可。
但不过每年的利息是可以加上本金再做下一年的本金,这种叫做复利。
所以只需要枚举年份,每次都做完全背包,在将每年的利息加本金累加,最后输出计数器即可。
优化:这样做的话最后一个点会被 Hack 掉,但通过仔细观察,题目说“且
a
a
a 一定是
1000
1000
1000 的倍数”,所以我们只需将每次与
a
a
a 和
s
s
s 相关的皆
÷
1000
\div 1000
÷1000 即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int s,n,d,w[15],val[15],dp[10000005];
int main(){
cin>>s>>n>>d;
for(int i=1;i<=d;i++)
{
cin>>w[i]>>val[i];
}
for(int year=1;year<=n;year++)
{
for(int i=1;i<=d;i++)
{
for(int j=w[i]/1000;j<=s/1000;j++)
{
dp[j]=max(dp[j],dp[j-w[i]/1000]+val[i]);
}
}
s+=dp[s/1000];
}
cout<<s;
return 0;
}
T 5 : P 1926 \tt T5:P1926 T5:P1926
前面是精卫填海,具体怎么写可以看这个,往后翻一点,前几篇写的不太好 。
这题先处理表示每项作业需要的时间,进行精卫填海的操作,之后有两种做法:
- 再做一遍01背包,维护每道刷的题的时间。
- 用贪心的思想,只要这题时间够,那么就做,再把时间减去,计数器统计做了多少道。
01背包做法:
#include<bits/stdc++.h>
using namespace std;
int n,m,k,r,w2[15],w1[15],val[15],dp[155],f[155];
int main(){
cin>>n>>m>>k>>r;
for(int i=1;i<=n;i++)
cin>>w2[i];
for(int i=1;i<=m;i++)
cin>>w1[i];
for(int i=1;i<=m;i++)
cin>>val[i];
for(int i=1;i<=m;i++)
for(int j=r;j>=w1[i];j--)
dp[j]=max(dp[j],dp[j-w1[i]]+val[i]);
for(int i=0;i<=r;i++)
if(dp[i]>=k){
r-=i;
break;
}
for(int i=1;i<=n;i++)
for(int j=r;j>=w2[i];j--)
f[j]=max(f[j],f[j-w2[i]]+1);
cout<<f[r];
return 0;
}
贪心做法:
sort(pro+1,pro+1+n);
for(int i=1;i<=n;i++)
{
if(now>=pro[i])
{
now-=pro[i];
ans++;
}
}
cout<<ans;
总结
第 2 , 5 2,5 2,5 都是细节错误, 4 4 4 题思路错误, 3 3 3 题真的不会正解。