前言:
最近学习了许多新的,高级的算法,但是------
由于dp过菜,愤然决定开始dp复习qaq
开始之前,关于动态规划:
应用范围:动态规划方法一般用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望找到具有最优值的解,我们称这样的解为问题的一个最优解,而不是最优解,因为可能有多个解都达到最优值。
解决动态规划问题一般分为四步:
1、定义一个状态,这是一个最优解的结构特征
2、进行状态递推,得到递推公式
3、进行初始化
4、返回结果
背包
01背包
首先是最基础的01背包,话不多说看题:
[LGOJ]P1048 采药,一个非常非常超级超级(懂的自然懂)裸的01背包;
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目
接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
拿到题,第一反应是设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i件物品,花费 t t t的时间所能采到最高价值的药,于是我们不难得到
- 当剩余时间无法采到该药时 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j]
- 否则 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − c o s t [ i ] ] + v a l [ i ] , d p [ i − 1 ] [ j ] ) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]) dp[i][j]=max(dp[i−1][j−cost[i]]+val[i],dp[i−1][j])
于是上代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int t,m;
int dp[maxn][maxn],cost[maxn],val[maxn];
inline int read()
{
int s=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if (c=='-')
{
f=-1;
}
c=getchar();
}
while (c>='0'&&c<='9')
{
s=s*10+c-48;
c=getchar();
}
return s*f;
}
int main()
{
t=read(),m=read();
for (int i=1;i<=m;i++)
{
cost[i]=read(),val[i]=read();
}
for (int i=1;i<=m;i++)
{
for (int j=t;j>=0;j--)
{
if (cost[i]>j)
{
dp[i][j]=dp[i-1][j];
}
else
{
dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]);
}
}
}
cout<<dp[m][t]<<endl;
return 0;
}
嗯,这种方法看起来还不错,但是空间达到了
O
(
N
∗
M
)
O(N*M)
O(N∗M)!!!,我们又发现
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]这一状态全部都是来自于
i
−
1
i-1
i−1,于是不难想到,我们可以把第一维给优化掉得到:
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
j
−
c
o
s
t
[
i
]
]
+
v
a
l
[
i
]
)
;
dp[j]=max(dp[j],dp[j-cost[i]]+val[i]);
dp[j]=max(dp[j],dp[j−cost[i]]+val[i]);
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int t,m;
int dp[maxn],cost[maxn],val[maxn];
inline int read()
{
int s=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if (c=='-')
{
f=-1;
}
c=getchar();
}
while (c>='0'&&c<='9')
{
s=s*10+c-48;
c=getchar();
}
return s*f;
}
int main()
{
t=read(),m=read();
for (int i=1;i<=m;i++)
{
cost[i]=read(),val[i]=read();
}
for (int i=1;i<=m;i++)
{
for (int j=t;j>=cost[i];j--)
{
dp[j]=max(dp[j],dp[j-cost[i]]+val[i]);
}
}
cout<<dp[t]<<endl;
return 0;
}
例题2:[LGOJ]P1802 5倍经验日
题面:
题目描述
现在absi2011拿出了x个迷你装药物(嗑药打人可耻….),准备开始与那些人打
由于迷你装一个只能管一次,所以absi2011要谨慎的使用这些药,悲剧的是,没到达最少打败该人所用的属性药了他打人必输>.<所以他用2个药去打别人,别人却表明3个药才能打过,那么相当于你输了并且这两个属性药浪费了
现在有n个好友,有输掉拿的经验、赢了拿的经验、要嗑几个药才能打过。求出最大经验(注意,最后要乘以5
输入格式
第一行两个数,n和
后面n行每行三个数,分别表示输了拿到的经验(
l
o
s
e
[
i
]
lose[i]
lose[i])、赢了拿到的经验(
w
i
n
[
i
]
win[i]
win[i])、打过要至少使用的药数量(
u
s
e
[
i
]
use[i]
use[i])
输出格式
一个整数,最多获得的经验
这题稍微有些变形,但是难度比较小。
看完题自然而然想到
d
p
[
i
]
dp[i]
dp[i]表示
i
i
i瓶药能拿到的最大经验,于是状态转移方程便很显而易见了:
当剩余的药还能打这一个朋友时
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]);
dp[j]=max(dp[j]+lose[i],dp[j−use[i]]+win[i]);
否则
d
p
[
j
]
+
=
l
o
s
e
[
i
]
;
dp[j]+=lose[i];
dp[j]+=lose[i];
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1000005;
int n,x;
int dp[maxn],lose[maxn],win[maxn],use[maxn];
inline int read()
{
int s=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if (c=='-')
{
f=-1;
}
c=getchar();
}
while (c>='0'&&c<='9')
{
s=s*10+c-48;
c=getchar();
}
return s*f;
}
signed main()
{
n=read(),x=read();
for (int i=1;i<=n;i++)
{
lose[i]=read(),win[i]=read(),use[i]=read();
}
for (int i=1;i<=n;i++)
{
for (int j=x;j>=0;j--)
{
if (j>=use[i])
{
dp[j]=max(dp[j]+lose[i],dp[j-use[i]]+win[i]);
}
else
{
dp[j]+=lose[i];
}
}
}
cout<<5*dp[x]<<endl;
return 0;
}
ok,最基础的01背包就到这里了,接下来我们要迈进下一步了~~~~