本篇用于记录NX的动态规划学习的所有想法以及部分题目,对于所有出现的题目,我会尽量不用宏定义的(滑稽) 。
最近这段时间,学习了神奇的东西:动态规划
首先来看看定义:动态规划( d y n a m i c p r o g r a m m i n g dynamic programming dynamicprogramming)是运筹学的一个分支,是求解决策过程( d e c i s i o n p r o c e s s decision process decisionprocess)最优化的数学方法。 20 20 20世纪 50 50 50年代初美国数学家 R . E . B e l l m a n R.E.Bellman R.E.Bellman等人在研究多阶段决策过程( m u l t i s t e p d e c i s i o n p r o c e s s multistep decision process multistepdecisionprocess)的优化问题时,提出了著名的最优化原理( p r i n c i p l e o f o p t i m a l i t y principle of optimality principleofoptimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。 1957 1957 1957年出版了他的名著 《 D y n a m i c P r o g r a m m i n g 》 《Dynamic Programming》 《DynamicProgramming》,这是该领域的第一本著作。
然而,定义关我们什么事,但是,有句话还是很有用的:
“多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解”
这句话说明了,动态规划需要状态划分
再来一段:
阶段:
把所给求解问题的过程恰当的分成若干互相联系的阶段,以便于求解。一般阶段就是递归的最外侧循环。
状态:
表示每个阶段中面临的自然状况或客观条件,是不可控制因素。
状态转移:
从一个阶段的一个阶段转移到下一阶段的某个状态的一种选择行为,叫做状态转移。
没错,这些都是我从书上抄下来的,大概读读就行。
先看DP入门的第一题,
#1: 【dp开始】数字三角形
题目描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大
输入格式
第一个行包含 R R R( 1 < = R < = 1000 1<= R<=1000 1<=R<=1000) ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于 100 100 100。
输出格式
单独的一行,包含那个可能得到的最大的和。
样例数据
input
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
output
30
数据规模与约定
usaco1.5.1
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
这题呢有种非常暴力的解法,就是两重循环,每次 a [ i ] [ j ] a[i][j] a[i][j]都由上面两个与其接近的数的最大值。最后再对 a [ r ] [ i ] a[r][i] a[r][i]取 m a x max max。
Code:
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
const int twx=1000+10;
int a[twx][twx];
int r;
int sum;
void init()
{
cin>>r;
f(1,r,i)
{
f(1,i,j)
{
cin>>a[i][j];
}
}
}
void work()
{
f(1,r,i)
{
f(1,i,j)
{
a[i][j]=a[i][j]+max(a[i-1][j],a[i-1][j-1]);
}
}
}
void print()
{
f(1,r,i)
{
sum=max(a[r][i],sum);
}
cout<<sum;
}
int main()
{
//freopen("numtri.in","r",stdin);
//freopen("numtri.out","w",stdout);
init();
work();
print();
}
非常水对吧,我说过我不会用宏定义的,没毛病吧。
我们再来看看另一个神奇的东西:
最优化原理和最优子结构
最优化策略具有这样的性质,不论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
动态规划的过程就是有局部最优解推出最终最优解的过程。
看看以下的例子是否符合最优子结构:
【例题】如图,已知一个有向图,求一条从最左边的点到最右边点的方案(只能从左往右走),使得所经过的权值和除以4的余数最小
【分析】给所有的点编号1,2,3,4。f[i]表示前i个点的最优值,易得到一个方程
f
[
i
]
=
m
i
n
(
f
[
i
−
1
]
+
n
u
m
[
i
−
1
]
[
1
]
)
m
o
d
4
,
f
[
i
−
1
]
+
n
u
m
[
i
−
1
]
[
2
]
m
o
d
4
f[i] = min { ( f[i-1]+num[i-1][1] ) mod 4,f[i-1]+num[i-1][2] mod 4}
f[i]=min(f[i−1]+num[i−1][1])mod4,f[i−1]+num[i−1][2]mod4
通过这个方程可以求出一条路径为
(
2
+
3
+
1
)
m
o
d
4
=
2
(2+3+1)mod 4=2
(2+3+1)mod4=2
然而,最优值是 ( 2 + 1 + 1 ) m o d 4 = 0 (2+1+1)mod 4=0 (2+1+1)mod4=0
为什么会出错呢???
观察以上数据发现取
m
i
n
(
3
)
min(3)
min(3)的时候,动态规划求出来的最优值是
1
1
1,而正确的值应该是
0
0
0,由此可知本题对应一条最优路径,并不是这条路径上的所有点的最优值都是从点
1
1
1到该点可得的最优值。
还有几个很重要的东西是:
无后效性
无后效性,即在此后过程的演变中不再收到此前的各种状态以及决策的影响,简单的说,就是“未来与过去物管”,当前的状态是此前历史的一个完整总结,此前的历史只能通过当前的状态去影响过程未来的演变。
子问题的重叠性
动态规划实际上就是搜索的优化,可以将原本就要指数级别的搜索算法改进为具有多项式复杂度的算法,原因就是解决了搜索中的重复的子问题,这是动态规划的根本目的。
好了废话讲完了(其实这些是DP时时都得注意的东西)
接下来根据提高篇的安排,我来继续写下去了(我可是要写1w字的报告的)
(1)首先是资源分配类的题目。
说到资源分配,我们就不得不说下机器分配那道题了。
#2:【资源分配dp】机器分配
题目描述
某总公司拥有高效生产设备M台,准备分给下属的 N N N个分公司。各分公司若获得这些设备,可以为总公司提供一定的盈利。问:如何分配这 M M M台设备才能使公司得到的盈利最大?求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数 M M M。其中 M < = 100 M<=100 M<=100, N < = 100 N<=100 N<=100。
输入格式
第一行为两个整数 M M M, N N N。接下来是一个 N × M N×M N×M的矩阵,其中矩阵的第 i i i行的第 j j j列的数 A i j A_{ij} Aij表明第 i i i个公司分配 j j j台机器的盈利。所有数据之间用一个空格分隔。
输出格式
只有一个数据,为总公司分配这 M M M台设备所获得的最大盈利。
样例数据
input
3 2
1 2 3
2 3 4
output
4
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题呢,还是比较水的,定义一个数组:
b
[
i
]
[
j
]
b[i][j]
b[i][j]表示前i个公司分配j台机器的最大盈利。
通过枚举第
i
i
i个公司需要多少台机器,再枚举第
i
−
1
i-1
i−1个公司需要多少机器,再找出最大值就可以了
Code:
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=100+100;
int m,n;
int a[twx][twx]={};
int b[twx][twx]={};
int ans=0;
void init()
{
in>>m>>n;
f(1,n,i)
{
f(1,m,j)
{
in>>a[i][j];
}
}
}
void work()
{
f(1,n,i)
{
f(1,m,j)
{
f(0,j,k)
{
b[i][j]=max(b[i][j],b[i-1][j-k]+a[i][k]);
}
}
}
}
void print()
{
out<<b[n][m];
}
int main()
{
//freopen("allot.in","r",stdin);
//freopen("allot.out","w",stdout);
init();
work();
print();
return 0;
}
这里使用了二维数组,用来维护前i个公司得到j台机器所获得的的最优值。
#3:复制书稿
题目描述
现在要把 m m m本有顺序的书分给 k k k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。
现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
输入格式
第一行两个整数 m m m, k k k;( k ≤ m ≤ 500 k≤m≤500 k≤m≤500)
第二行 m m m个整数,第 i i i个整数表示第 i i i本书的页数
输出格式
共 k k k行,每行两个整数,第 i i i行表示第 i i i个人抄写的书的起始编号和终止编号。
k k k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。
样例数据
input
9 3
1 2 3 4 5 6 7 8 9
output
1 5
6 7
8 9
数据规模与约定
经典问题
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题呢,
a
s
d
[
i
]
[
j
]
asd[i][j]
asd[i][j]表示前i个人抄写前j本书所用的最长时间,这里的最长时间是某个人抄书的时间。
a
s
d
[
i
]
[
j
]
=
m
i
n
(
a
s
d
[
i
]
[
j
]
,
m
a
x
(
a
s
d
[
i
−
1
]
[
l
]
,
s
u
m
[
j
]
−
s
u
m
[
l
]
)
)
asd[i][j]=min(asd[i][j],max(asd[i-1][l],sum[j]-sum[l]))
asd[i][j]=min(asd[i][j],max(asd[i−1][l],sum[j]−sum[l]));(这个给人一种要二分的冲动)。
Code:
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=500+100;
int m,k;
int ans;
int asd[twx][twx];
int a[twx];
int sum[twx];
void init()
{
memset(asd,100,sizeof(asd));
in>>m>>k;
f1(1,m,i)
{
in>>a[i];
sum[i]=sum[i-1]+a[i];
asd[1][i]=sum[i];
}
}
void work()
{
f1(2,k,i)
{
f1(1,m,j)
{
f1(1,j-1,l)
{
asd[i][j]=min(asd[i][j],max(asd[i-1][l],sum[j]-sum[l]));
}
}
}
}
void print(int i,int j)
{
if(i==0)
{
return ;
}
if(i==1)
{
out<<"1 1"<<endl;
return ;
}
else
{
int temp=i;
int x=a[i];
while(x+a[temp-1]<=asd[k][m]&&temp>1)
{
x+=a[--temp];
}
print(temp-1,j-1);
out<<temp<<" "<<i<<endl;
}
}
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
init();
work();
print(m,k);
return 0;
}
(2)接下来是背包类问题
背包问题作为资源分配的一个分支,为什么要单独拉出来呢,因为有《背包九讲》啊!
I.0/1背包问题
【问题】有n件物品和一个容量为C的背包。第i件物品的重量为w[i],价值为v[i]。
求解将那些物品装入背包可使价值总和最大。
1)二维数组表示
1.状态:
f
[
i
]
[
c
]
f[i][c]
f[i][c]表示前
i
i
i件物品恰放入一个容量为
c
c
c的背包可以获得的最大价值
2.状态转移方程
f
[
i
]
[
c
]
=
m
a
x
{
f
[
i
−
1
]
[
c
]
不
选
f
[
i
−
1
]
[
c
−
w
[
i
]
]
+
v
[
i
]
选
f[i][c]=max \left\{\begin{aligned}f[i-1][c] 不选 \\ f[i-1][c-w[i]]+v[i]选 \end{aligned}\right.
f[i][c]=max{f[i−1][c]不选f[i−1][c−w[i]]+v[i]选
核心代码:
for(int i=1;i<=n;i++)
{
for(int c=0;c<=C;c++)
{
f[i][c]=f[i-1][c];
if(c>=w[i])
{
f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]);
}
}
}
时间复杂度,空间复杂度都为O(NC)
#优化:
对于 f [ i ] [ c ] f[i][c] f[i][c]只与 f [ i − 1 ] [ c ] f[i-1][c] f[i−1][c]和 f [ i − 1 ] [ c − w [ i ] f[i-1][c-w[i] f[i−1][c−w[i]有关
所以可将 i i i这个维数优化掉
进一步发现 f [ i ] [ c ] f[i][c] f[i][c]只与 f [ i − 1 ] [ c ] f[i-1][c] f[i−1][c]和 f [ i − 1 ] [ c − w [ i ] f[i-1][c-w[i] f[i−1][c−w[i]有关
由此,第 i i i层的 f [ c ] f[c] f[c]只与第 i − 1 i-1 i−1层的 f [ c ] f[c] f[c]和 f [ c − w [ i ] ] f[c-w[i]] f[c−w[i]]有关
所以在求 f [ c ] f[c] f[c]的时候,必须保证 f [ c − w [ i ] ] f[c-w[i]] f[c−w[i]]是第 i − 1 i-1 i−1阶段的最优值, c c c从 C C C开始倒着推
保证 f [ c ] f[c] f[c]左边的状态没有被第 i i i个物品更新过,还是第 i − 1 i-1 i−1层的状态。
Code:
for(int i=1;i<=n;i++)
{
for(int c=C;c>=0;c--)
{
if(c>=w[i])
{
f[c]=max(f[c],[c-w[i]]+v[i]);
}
}
}
时间 O ( N C ) O(NC) O(NC)
空间 O ( C ) O(C) O(C)
##常数优化
int bound,sumw=0;
for(int i=1;i<=n;i++)
{
sumw+=w[i];
bound=max(c-sumw,w[i]);
for(int c=C;c>=bound;c--)
{
if(c>=w[i])
{
f[c]=max(f[c],[c-w[i]]+v[i]);
}
}
}
###细节
若要恰好装满则使 f [ 0 ] = 0 f[0]=0 f[0]=0,其余负无穷,如此可知是否有解
若不要恰好 m e m s e t ( 0 ) memset(0) memset(0)即可。
#4.【背包】采药
题目描述
宁智贤是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。
如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是宁智贤,你能完成这个任务吗?
输入格式
输入的第一行有两个整数 T T T( 1 < = T < = 1000 1 <= T <= 1000 1<=T<=1000)和 M M M( 1 < = M < = 100 1 <= M <= 100 1<=M<=100),用一个空格隔开,T代表总共能够用来采药的时间, M M M代表山洞里的草药的数目。接下来的M行每行包括两个在 1 1 1到 100 100 100之间(包括 1 1 1和 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例数据
input
70 3
71 100
69 1
1 2
output
3
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
这是一道模板题,就不写题解了
Code:
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=10000+100;
int t,m;
struct LV
{
int shijian;
int jiazhi;
}a[twx];
int asd[twx];
void init()
{
in>>t>>m;
f(1,m,i)
{
in>>a[i].shijian>>a[i].jiazhi;
}
}
void work()
{
f(1,m,i)
{
for(int j=t;j>=a[i].shijian;j--)
{
asd[j]=max(asd[j],asd[j-a[i].shijian]+a[i].jiazhi);
}
}
}
void print()
{
out<<asd[t];
}
int main()
{
//freopen("input.in","r",stdin);
//freopen("output.out","w",stdout);
init();
work();
print();
return 0;
}
II.完全背包
有
n
n
n种物品和一个容量为为
C
C
C的背包第
i
i
i件物品的重量是
w
[
i
]
w[i]
w[i],价值为
v
[
i
]
v[i]
v[i],数量无限。求解将哪些物品装入背包可使价格总和最大
1.将其转化为
0
/
1
0/1
0/1。不同点在于物品有无数件。
同样令
f
[
i
]
[
c
]
f[i][c]
f[i][c]表示前i种物品恰放入一个容量为
v
v
v的背包的最大值。
f
[
i
]
[
c
]
=
m
a
x
(
f
[
i
−
1
]
[
c
−
k
∗
w
[
i
]
]
+
k
∗
v
[
i
]
)
0
≤
k
∗
w
[
i
]
≤
C
f[i][c]=max(f[i-1][c-k*w[i]]+k*v[i]) 0\leq k*w[i] \leq C
f[i][c]=max(f[i−1][c−k∗w[i]]+k∗v[i])0≤k∗w[i]≤C
时间复杂度
O
(
N
V
∑
(
j
/
C
[
i
]
)
)
O(NV\sum(j/C[i]))
O(NV∑(j/C[i]))
#简单优化:
1.若物品
i
i
i,
j
j
j满足
w
[
i
]
≤
w
[
j
]
w[i]\leq w[j]
w[i]≤w[j]且
v
[
j
]
≤
v
[
i
]
v[j]\leq v[i]
v[j]≤v[i]就不取
j
j
j
2.将重量大于
C
C
C的物品去掉
##更优
for(int i=1;i<=n;i++)
{
for(int c=0;c<=C;c++)
{
if(c>=w[i])
{
f[c]=max(f[c],f[c-w[i]]+v[i]);
}
}
}
时间复杂度
O
(
N
C
)
O(NC)
O(NC)
变为二维
f
[
i
]
[
c
]
=
m
a
x
{
f
[
i
−
1
]
[
c
]
f
[
i
−
1
]
[
c
−
w
[
i
]
]
+
v
[
i
]
f[i][c]=max \left\{\begin{aligned}f[i-1][c] \\ f[i-1][c-w[i]]+v[i] \end{aligned}\right.
f[i][c]=max{f[i−1][c]f[i−1][c−w[i]]+v[i]
如此美妙。正着推
f
[
c
−
w
[
i
]
]
f[c-w[i]]
f[c−w[i]]的值已经被第
i
i
i个物品更新过了,即表明第
i
i
i个物品可以随时使用,就是不受个数的限制,就成了完全背包
#5:完全背包
题目描述
有一个负重能力为 m m m( m < = 300 m<=300 m<=300)的背包和 n n n( n < = 100 n<=100 n<=100)种物品,第i种物品的价值为 v [ i ] v[i] v[i],重量为 w [ i ] w[i] w[i]。
在不超过背包负重能力的前提下选择若干个物品装入背包,使这些物品的价值之和最大。每种物品可以不选,也可以选择多个。假设每种物品都有足够的数量。
输入格式
第1行 m m m , n n n
第2行到 n + 1 n+1 n+1行 每行两个数据,分别为每种物品的重量和价值,空格隔开。
输出格式
装入背包物品的最大价值
样例数据
input
12 4
2 1
3 3
4 5
7 9
output
max=15
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
还是模板题
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=100+100;
int m,n;
int v[twx];
int w[twx];
int asd[twx*20];
void init()
{
in>>m>>n;
f(1,n,i)
{
in>>w[i]>>v[i];
}
}
void work()
{
f(1,n,i)
{
f(1,m,j)
{
if(j>=w[i])
{
asd[j]=max(asd[j],asd[j-w[i]]+v[i]);
}
}
}
}
void print()
{
out<<"max="<<asd[m];
}
int main()
{
//freopen("knapsack.in","r",stdin);
//freopen("knapsack.out","w",stdout);
init();
work();
print();
return 0;
}
#6:质数和分解
题目描述
任何大于 1 1 1 的自然数 n n n都可以写成若干个大于等于 2 2 2且小于等于 n n n的质数之和表达式(包括只有一个数构成的和表达式的情况),并且可能有不止一种质数和的形式。
例如, 9 9 9 的质数和表达式就有四种本质不同的形式:
9 = 2 + 5 + 2 = 2 + 3 + 2 + 2 = 3 + 3 + 3 = 2 + 7 9=2+5+2=2+3+2+2=3+3+3=2+7 9=2+5+2=2+3+2+2=3+3+3=2+7。
这里所谓两个本质相同的表达式是指可以通过交换其中一个表达式中 参加和运算的各个数的位置而直接得到另一个表达式。
试编程求解自然数 n n n可以写成多少种本质不同的质数和表达式。
输入格式
一行存放一个自然数 n n n ( 2 < = n < = 500 ) (2<= n <=500) (2<=n<=500)。
输出格式
输出自然数 n n n的本质不同的质数和表达式的数目。
样例数据
input
2
output
1
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题就是将一个数分成由质数累加的形式,求方案数;
将质数看成物品的体积,完全背包求塞满体积的方案数
Code:
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=1000+100;
int n;
int asd[twx];
int b[twx];
bool a[twx];
int dfg=0;
void init()
{
in>>n;
}
void work_1()
{
f1(2,n,i)//筛
{
if(a[i])
{
continue ;
}
b[++dfg]=i;
f1(1,n/i,j)
{
a[i*j]=true;
}
}
}
void work_2()
{
asd[0]=1;
f1(1,dfg,i)
{
f1(b[i],n,j)
{
asd[j]=asd[j]+asd[j-b[i]];
}
}
}
void print()
{
out<<asd[n];
}
int main()
{
//freopen("input.in","r",stdin);
//freopen("output.out","w",stdout);
init();
work_1();
work_2();
print();
return 0;
}
III.多重背包
有 n n n种物品和一个容量为 C C C的背包。第i种物品的重量为 w [ i ] w[i] w[i]价格为v[i],数量为a[i],求解将哪些物品装入背包可使价值总和最大
!高能!
二进制:以二进制进行分割
for(int i=1;i<n;i++)
{
if(w[i]*a[i]>c)
{
for(int c=0;c<=C;c++)
{
if(c>=w[i])
{
f[c]=max(f[c],f[c-w[i]]+v[i]);
}
}
}
else
{
int k=1;
int amount=a[i];
while(k<amount)
{
for(int c=C;c>=k*w[i];c--)
{
f[c]=max(f[c].f[c-k*w[i]]+k*v[i]);
}
amount-=k;
k+=k;
}
for(int c=C;c>=amount*w[i];c--)
{
f[c]=max(f[c],f[c-amount*w[i]]+amount*v[i]);
}
}
}
#7:逃亡的准备
题目描述
在《Harry Potter and the Deathly Hallows》中,Harry Potter他们一起逃亡,现在有许多的东西要放到赫敏的包里面,但是包的大小有限,所以我们只能够在里面放入非常重要的物品,现在给出该种物品的数量、体积、价值的数值,希望你能够算出怎样能使背包的价值最大的组合方式,并且输出这个数值,赫敏会非常地感谢你。
输入格式
(1)第一行有 2 2 2个整数,物品种数 n n n和背包装载体积 v v v。
(2) 2 2 2行到 n + 1 n+1 n+1行每行 3 3 3个整数,为第 i i i种物品的数量 m m m、体积 w w w、价值 s s s。.
输出格式
仅包含一个整数,即为能拿到的最大的物品价值总和。
样例数据
input
2 10
3 4 3
2 2 5
output
13
【注释】
选第一种一个,第二种两个。
结果为 3 ∗ 1 + 5 ∗ 2 = 13 3*1+5*2=13 3∗1+5∗2=13
数据规模与约定
对于100%的数据
1 < = v < = 5000 1<=v<=5000 1<=v<=5000
1 < = n < = 5000 1<=n<=5000 1<=n<=5000
1 < = m < = 5000 1<=m<=5000 1<=m<=5000
1 < = w < = 5000 1<=w<=5000 1<=w<=5000
1 < = s < = 5000 1<=s<=5000 1<=s<=5000
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题呢就是把物品按照二进制分割就好了,还是模板题
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=100000+100;
int n,v;
int w[twx],s[twx];
int len,k;
int shuru1,shuru2,shuru3;
int asd[twx]={};
void init()
{
in>>n>>v;
len=1;
f(1,n,i)
{
in>>shuru1>>shuru2>>shuru3;
k=1;//二进制多重背包
while(k<=shuru1)
{
w[++len]=shuru2*k;
s[len]=shuru3*k;
shuru1-=k;
k*=2;
}
if(shuru1>0)//剩下的
{
w[++len]=shuru2*shuru1;
s[len]=shuru3*shuru1;
}
}
}
void work()
{
f(1,len,i)
{
for(int j=v;j>=w[i];j--)
{
asd[j]=max(asd[j],asd[j-w[i]]+s[i]);
}
}
}
void print()
{
out<<asd[v];
}
int main()
{
//freopen("cx.in","r",stdin);
//freopen("cx.out","w",stdout);
init();
work();
print();
return 0;
}
IV.混合背包
还是背包问题,但是有的物品只能取一次(0/1背包),有的可以取无限次(完全背包),有的只能取有限次(多重背包)。如何解决?
伪代码:
for(iny i=1;i<N;i++)
{
if(物品i属于0/1背包)
{
按照0/1背包做法取物品i
}
if(物品i属于完全背包)
{
按照完全背包做法取物品i
}
if(物品i属于多重背包)
{
按照多重背包做法取物品i
}
}
V.二维费用的背包问题
有
n
n
n件物品和一个容量为
C
C
C、容积为
U
U
U的背包。第
i
i
i件物品的重量是
w
[
i
]
w[i]
w[i],体积是
u
[
i
]
u[i]
u[i],价值是
v
[
i
]
v[i]
v[i]。
求解将哪些物品装入背包可使价值总和最大
(1).
0
/
1
0/1
0/1背包的表示方法
费用加了一维,只需把状态也加一维。
1.状态表示:设 f [ i ] [ c ] [ u ] f[i][c][u] f[i][c][u]为前i件物品付出两种代价分别为 c c c和 u u u时可以获得的最大价值。
2.状态转移方程:
f [ i ] [ c ] [ u ] = m a x { f [ i − 1 ] [ c ] [ u ] f [ i − 1 ] [ c − w [ i ] ] [ u − u [ i ] ] + v [ i ] f[i][c][u]=max \left\{\begin{aligned}f[i-1][c][u] \\ f[i-1][c-w[i]][u-u[i]]+v[i] \end{aligned}\right. f[i][c][u]=max{f[i−1][c][u]f[i−1][c−w[i]][u−u[i]]+v[i]
当然,为了节省空间,可以把 i i i去掉
3.启示:当发现由熟悉的动态规划题目变形而来的题目时,在原来的状态中加一维以满足新的限制,这是一种比较通用的方法。
(2).限制物品总个数的0/1背包
有 n n n件物品和一个容量为 C C C的背包。第 i i i件物品的重量是 w [ i ] w[i] w[i],价值是 v [ i ] v[i] v[i]。
现在要求转入背包的物品个数不超过 M M M。求解将哪些物品装入背包可使价值总和最大。
其实,把最大个数看做一种容积就行了。
(3).二维费用的完全背包和多重背包问题
循环时仍然按照完全背包(顺序循环)和多重背包(分割)的方法操作,只不过比完全背包和多重背包多了一维。
VI.分组背包问题
有
n
n
n件物品和一个容量为
C
C
C的背包。第i件物品的重量是
w
[
i
]
w[i]
w[i],价值是
v
[
i
]
v[i]
v[i]。这些物品被划分为
K
K
K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使价值总和最大。
1.状态表示:设
f
[
k
]
[
c
]
f[k][c]
f[k][c]为前
k
k
k组物品花费
c
c
c 时可以获得的最大价值。
2.状态转移方程:
f [ k ] [ c ] = m a x { f [ k − 1 ] [ c ] f [ k − 1 ] [ c − w [ i ] ] + v [ i ] 物 品 i 属 于 第 k 组 f[k][c]=max \left\{\begin{aligned}f[k-1][c] \\ f[k-1][c-w[i]]+v[i]物品i属于第k组 \end{aligned}\right. f[k][c]=max{f[k−1][c]f[k−1][c−w[i]]+v[i]物品i属于第k组
VII.有依赖的背包问题
#8:金明的预算方案
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“ 你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N N N 元钱就行 ”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
---|---|
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 0 0个、 1 1 1 个或 2 2 2 个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 N N N 元。于是,他把每件物品规定了一个重要度,分为 5 5 5 等:用整数 1 ∼ 5 1∼5 1∼5表示,第 5 5 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 10 10 元的整数倍)。他希望在不超过 N N N 元(可以等于 N N N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j j j件物品的价格为 v [ j ] v[j] v[j],重要度为 w [ j ] w[j] w[j],共选中了 k k k 件物品,编号依次为 j 1 j1 j1, j 2 j2 j2,⋯⋯, j k jk jk,则所求的总和为:
v [ j 1 ] × w [ j 1 ] + v [ j 2 ] × w [ j 2 ] + ⋯ + v [ j k ] × w [ j k ] v[j1]×w[j1]+v[j2]×w[j2]+⋯+v[jk]×w[jk] v[j1]×w[j1]+v[j2]×w[j2]+⋯+v[jk]×w[jk]
请你帮助金明设计一个满足要求的购物单。
输入格式
输入文件 budget.in 的第
1
1
1 行,为两个正整数,用一个空格隔开:
N
N
N
m
m
m
(其中
N
N
N(
<
32000
<32000
<32000)表示总钱数,
m
m
m(
<
60
<60
<60)为希望购买物品的个数)
从第
2
2
2 行到第
m
+
1
m+1
m+1 行,第
j
j
j 行给出了编号为
j
−
1
j−1
j−1 的物品的基本数据,每行有
3
3
3 个非负整数
v
v
v
p
p
p
q
q
q
(其中
v
v
v 表示该物品的价格(
v
<
10000
v<10000
v<10000),p 表示该物品的重要度(
1
∼
5
1∼5
1∼5),
q
q
q 表示该物品是主件还是附件。如果
q
=
0
q=0
q=0,表示该物品为主件,如果
q
>
0
q>0
q>0,表示该物品为附件,
q
q
q 是所属主件的编号)
输出格式
输出文件 budget.out 只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( < 200000 <200000 <200000)。
样例数据
input
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
output
2200
数据规模与约定
时间限制:1s
空间限制:10MB
本题只考虑主件,设 a s d [ i ] [ j ] asd[i][j] asd[i][j](也可以使用一维数组),然后对于每一个主件,选取用哪些附件,可以说本题实际上是两层的,一个主件 0 / 1 0/1 0/1背包,在枚举当前主件空间的时候,我们在做一次基于附件的 0 / 1 0/1 0/1背包。
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=70+100;
int n,m;
int v[twx],w[twx],q[twx];
int asd[twx*300];
int a[twx*300];
void init()
{
in>>n>>m;
f(1,m,i)
{
in>>v[i]>>w[i]>>q[i];
w[i]*=v[i];
}
}
void work()
{
f(1,m,i)
{
if(q[i]==0)
{
f(1,v[i],j)
{
a[j]=0;
}
f(v[i],n,j)
{
a[j]=asd[j-v[i]]+w[i];
}
f(1,m,j)
{
if(q[j]==i)
{
for(int k=n;k>=v[i]+v[j];k--)
{
a[k]=max(a[k],a[k-v[j]]+w[j]);
}
}
}
f(v[i],n,j)
{
if(a[j]>asd[j])
{
asd[j]=a[j];
}
}
}
}
}
void print()
{
work();
out<<asd[n];
}
int main()
{
//freopen("budget.in","r",stdin);
//freopen("budget.out","w",stdout);
init();
print();
return 0;
}
关于背包的内容大概就到这了,然而,这只是我们上了的,其实还有好多,所以,等我学完我会来更新的。
(3)接下来是双进程类问题
双进程类动态规划,看名字就知道有两个同时进行的决策,两个决策不是相互独立,而是相互影响的。
首先来看一道题目:最长公共子序列
#9:最长公共子序列
题目描述
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。
令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij = yj。
例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列。
输入格式
第1行为第1个字符序列,都是大写字母组成,以”.”结束。长度小于5000。
第2行为第2个字符序列,都是大写字母组成,以”.”结束,长度小于5000。
输出格式
输出上述两个最长公共自序列的长度。
样例数据
input
ABCBDAB.
BACBBD.
output
4
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题呢,我们需要知道的是两个字符串的LCS的长度,既然是公共,也就是说这两个字符串都包含了这个字符串,用asd[i][j]记录序列x的前i位和序列y的前j位的最长公共子序列的长度。
a s d [ i + 1 ] [ j + 1 ] = m a x ( a s d [ i ] [ j + 1 ] , a s d [ i + 1 ] [ j ] ) asd[i+1][j+1]=max(asd[i][j+1],asd[i+1][j]) asd[i+1][j+1]=max(asd[i][j+1],asd[i+1][j])
a s d [ i + 1 ] [ j + 1 ] = m a x ( a s d [ i + 1 ] [ j + 1 ] , a s d [ i ] [ j ] + 1 ) ( s 1 [ i ] = = s 2 [ j ] ) asd[i+1][j+1]=max(asd[i+1][j+1],asd[i][j]+1)(s1[i]==s2[j]) asd[i+1][j+1]=max(asd[i+1][j+1],asd[i][j]+1)(s1[i]==s2[j])
Code:
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=5000+100;
string s1,s2;
int asd[twx][twx];
int len1,len2;
void init()
{
memset(asd,0,sizeof(asd));
in>>s1>>s2;
}
void work()
{
len1=s1.size()-1;
len2=s2.size()-1;
f1(0,len1-1,i)
{
f1(0,len2-1,j)
{
asd[i+1][j+1]=max(asd[i][j+1],asd[i+1][j]);
if(s1[i]==s2[j])
{
asd[i+1][j+1]=max(asd[i+1][j+1],asd[i][j]+1);
}
}
}
}
void print()
{
out<<asd[len1][len2];
}
int main()
{
//freopen("lcs.in","r",stdin);
//freopen("lcs.out","w",stdout);
init();
work();
print();
return 0;
}
再看一下这道:
#10:交错匹配
题目描述
有两行自然数,UP[1…N],DOWN[1…M],如果UP[I]=DOWN[J]=K,那么上行的第I个位置的数就可以跟下行的第J个位置的数连一条线,称为一条K匹配,但是同一个位置的数最多只能连一条线。
另外,每个K匹配都必须且至多跟一个L匹配相交且K≠L 。现在要求一个最大的匹配数。
例如:以下两行数的最大匹配数为8
输入格式
第一行有两个正整数N和M。第二行N个UP的自然数,第三行M个DOWN的自然数。其中0<N、M<=200,UP、DOWN的数都不超过32767。
输出格式
一个整数:最大匹配数
样例数据
input1
12 11
1 2 3 3 2 4 1 5 1 3 5 10
3 1 2 3 2 4 12 1 5 5 3
output1
8
input2
4 4
1 1 3 3
1 1 3 3
output2
0
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题呢
a
s
d
[
i
]
[
j
]
asd[i][j]
asd[i][j]表示第一个序列
s
1
s1
s1前
i
i
i个,第二个序列
s
2
s2
s2前
j
j
j个的最多的交叉数
本题得预处理出两个数组,
j
i
l
u
1
[
i
]
[
j
]
jilu1[i][j]
jilu1[i][j] 表示
s
1
s1
s1序列从
i
−
1
i-1
i−1开始,往左,
s
2
[
j
]
s2[j]
s2[j]第一次出现的位置
j
i
l
u
2
[
i
]
[
j
]
jilu2[i][j]
jilu2[i][j]表示
s
2
s2
s2序列从
j
−
1
j-1
j−1开始,往左,
s
1
[
i
]
s1[i]
s1[i]第一次出现的位置。
Code:
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=200+100;
int n,m;
int UP[twx]={};
int DOWN[twx]={};
int jilu1[twx][twx]={};
int jilu2[twx][twx]={};
int asd[twx][twx]={};
void init()
{
in>>n>>m;
f1(1,n,i)
{
in>>UP[i];
}
f1(1,m,i)
{
in>>DOWN[i];
}
}
void work_1()
{
f1(1,n,i)
{
f1(1,m,j)
{
f2(j-1,1,k)
{
if(UP[i]==DOWN[k])
{
jilu1[i][j]=k;
break ;
}
}
}
}
f1(1,m,i)
{
f1(1,n,j)
{
f2(j-1,1,k)
{
if(DOWN[i]==UP[k])
{
jilu2[i][j]=k;
break ;
}
}
}
}
}
void work_2()
{
f1(1,n,i)
{
f1(1,m,j)
{
asd[i][j]=max(asd[i-1][j],asd[i][j-1]);
if(UP[i]==DOWN[j])
{
continue ;
}
if(jilu1[i][j]!=0&&jilu2[j][i]!=0)
{
asd[i][j]=max(asd[i][j],asd[jilu2[j][i]-1][jilu1[i][j]-1]+2);
}
}
}
}
void print()
{
out<<asd[n][m];
}
int main()
{
//freopen("cross.in","r",stdin);
//freopen("cross.out","w",stdout);
init();
work_1();
work_2();
print();
return 0;
}
(4)接下来是区间动态规划
区间动态规划就如同它的名字一样,在区间上做DP。主要的方法是对于每段区间,他们的最优值都是由几段更小的区间的最优值得到。
非常重要的一个是:
大区间是由小区间推导而来的
#11: 【区间动规】石子合并
题目描述
在操场上沿一直线排列着 n n n堆石子。现要将石子有次序地合并成一堆。
规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆石子数计为该次合并的得分。
我们希望这 n − 1 n-1 n−1次合并后得到的得分总和最小。
输入格式
第一行有一个正整数 n n n( n < = 300 n<=300 n<=300),表示石子的堆数;
第二行有 n n n个正整数,表示每一堆石子的石子数,每两个数之间用一个空格隔开。它们都不大于 10000 10000 10000。
输出格式
一行,一个整数,表示答案。
样例数据
input
3
1 2 9
output
15
数据规模与约定
区间dp第一题
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
将
n
n
n堆石子合并可以分解为先将
[
1
,
k
]
[1,k]
[1,k]的石子合并,再将
[
k
+
1
,
n
]
[k+1,n]
[k+1,n]的石子合并。所以将区间
[
i
,
j
]
[i,j]
[i,j]的石子合并的子问题是将
[
i
,
k
]
[
k
+
1
,
j
]
[i,k][k+1,j]
[i,k][k+1,j] 的石子分别合并,最后再将这两堆石子合并起来。
然而当时为了方便我写了个记忆化搜索的版本:
code:
#include<bits/stdc++.h>
using namespace std;
#define f(a,b,c) for(int c=a;c<=b;c++)
#define so1(a,n,mycmp) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=300+100;
ll n;
ll asd[twx][twx];
ll sum[twx];
void init()
{
in>>n;
sum[0]=0;
memset(asd,-1,sizeof(asd));
f(1,n,i)
{
in>>sum[i];
sum[i]+=sum[i-1];
asd[i][i]=0;
}
}
ll work(ll dfg,ll ghj)
{
if(asd[dfg][ghj]!=-1)
{
return asd[dfg][ghj];
}
ll ans=1e9;
f(dfg,ghj-1,k)
{
ans=min(ans,sum[ghj]-sum[dfg-1]+work(dfg,k)+work(k+1,ghj));
}
return asd[dfg][ghj]=ans;
}
void print()
{
out<<work(1,n);
}
int main()
{
//freopen("Stone.in","r",stdin);
//freopen("Stone.out","w",stdout);
init();
print();
return 0;
}
说到区间就不得不说那道了
#12:【noip2007提高组】矩阵游戏
题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的$n* m 的 矩 阵 , 矩 阵 中 的 每 个 元 素 的矩阵,矩阵中的每个元素 的矩阵,矩阵中的每个元素a[i][j]$均为非负整数。游戏规则如下:
1.每次取数时须从每行各取走一个元素,共 n n n个。 m m m次后取完矩阵所有的元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和;每行取数的得分 = 被取走的元素值* 2 i 2^i 2i,其中i表示第 i i i次取数(从 1 1 1开始编号);
4.游戏结束总得分为 m m m次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入格式
包括 n + 1 n+1 n+1行;
第一行为两个用空格隔开的整数 n n n和 m m m。
第 2 2 2~ n + 1 n+1 n+1行为 n ∗ m n*m n∗m矩阵,其中每行有 m m m个用单个空格隔开
输出格式
仅包含1行,为一个整数,即输入矩阵取数后的最大的分。
样例数据
input
2 3
1 2 4
3 4 2
output
90
数据规模与约定
60%的数据满足: 1 < = n , m < = 30 1<=n, m<=30 1<=n,m<=30,答案不超过 1 0 16 10 ^{16} 1016
100%的数据满足: 1 < = n , m < = 80 1<=n, m<=80 1<=n,m<=80, 0 < = a [ i ] [ j ] < = 1000 0<=a[i][j]<=1000 0<=a[i][j]<=1000
时间限制: 1 s 1 \text {s} 1s
空间限制:
256
MB
256 \text {MB}
256MB
本题思路并不难,主要是写法上第一个是非常棒的手写万进制高精喽
让我们来看看rfy大佬的code:
#include<bits/stdc++.h>
using namespace std;
struct bignum
{
int len;
int n[150];
}f[100][100],maxx,a[100],maxxx;
void print(bignum c)
{
for(int i=c.len;i>=1;i--)
printf("%d",c.n[i]);
}
void add(bignum a,bignum b,bignum &c)
{
memset(&c,0,sizeof(c));
int len=0;
len=max(a.len,b.len);
for(int i=1;i<=len;i++)
{
c.n[i]+=a.n[i]+b.n[i];
if(c.n[i]>=10)
{
c.n[i+1]++;
c.n[i]-=10;
}
}
if(c.n[len+1]>0) len++;
c.len=len;
}
void init(string s1,bignum &a)
{ reverse(s1.begin(),s1.end());
for(int i=0;i<s1.size();i++)
a.n[i+1]=int(s1[i]-'0');
a.len=s1.size();
}
bignum vs(bignum a,bignum b)
{
if(a.len>b.len)
{
return(a);
}
if(a.len<b.len)
{
return(b);
}
for(int i=a.len;i>=1;i--)
{
if(a.n[i]>b.n[i])
{
return(a);
}
if(a.n[i]<b.n[i])
{
return(b);
}
}
}
int L,n;
int main()
{ freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&L,&n);
for(int l=1;l<=L;l++)
{
memset(&f,0,sizeof(f));//i--j
memset(&a,0,sizeof(a));
string s;
for(int i=1;i<=n;i++)
{
cin>>s;
init(s,a[i]);
}
for(int l=1;l<=n;l++)
for(int i=1;i<=n-l+1;i++)
{
int j=i+l-1;
bignum a1,a2,b1,b2;
add(f[i+1][j],f[i+1][j],a1);
add(a1,a[i],b1);
add(f[i][j-1],f[i][j-1],a2);
add(a2,a[j],b2);
f[i][j]=vs(f[i][j],b1);
f[i][j]=vs(f[i][j],b2);
}
add(f[1][n],maxx,maxx);
}
add(maxx,maxx,maxx);
print(maxx);
}
不太妙的是这个
于是我去luogu上学了个高效的非主流写法:手写int128
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=100+100;
struct int128
{
ll high;
ll low;
}sum,asd[twx][twx][twx],a[twx][twx],maxx;
int n,m;
ll p=1e18;
int128 max(int128 a,int128 b)
{
if(a.high>b.high) return a;
if(a.high<b.high) return b;
if(a.low>b.low) return a;
if(a.low<b.low) return b;
return a;
}
int128 operator+(int128 a,int128 b)
{
int128 k;
k.low=0;
k.high=0;
k.low=a.low+b.low;
k.high=k.low/p+a.high+b.high;
k.low%=p;
return k;
}
int128 operator*(int128 a,int b)
{
int128 k;
k.low=0,k.high=0;
k.low=a.low*b;
k.high+=k.low/p+b*a.high;
k.low%=p;
return k;
}
void init()
{
in>>n>>m;
f1(1,n,i)
{
f1(1,m,j)
{
in>>a[i][j].low;
}
}
}
void work()
{
f1(1,n,i)
{
f1(0,m-1,len)
{
for(int l=1;l+len<=m;l++)
{
asd[l][l+len][i]=max(asd[l+1][l+len][i]+a[i][l],asd[l][l+len-1][i]+a[i][l+len])*2;
}
}
}
f1(1,n,i)
{
sum=sum+asd[1][m][i];
}
}
void print()
{
if(sum.high==0)
{
out<<sum.low;
}
else
{
printf("%lld%018lld\n",sum.high,sum.low);
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
init();
work();
print();
return 0;
}
虽然空间上大了,但是时间上很完美啊。
(5)接下来是平面动态规划
平面dp也称二维dp
就是在一个平面上做dp,将状态由线性变成二维平面,其主要的思路和前面的类似
#13:农田个数
题目描述
你的老家在农村。过年时,你回老家去拜年。你家有一片 N × M N \times M N×M农田,将其看成一个 N × M N \times M N×M的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。
两个正方形农田不同必须至少包含下面的两个条件中的一条:
-
边长不相等
-
左上角的方格不是同一方格
输入格式
输入数据第一行为两个由空格分开的正整数N、M(1<=m< n <=1000)
第2行到第N+1行每行有M个数字(0或1),描述了这一片农田。0表示这个方格为水域,否则为农田(注意:数字之间没有空格,而且每行不会出现空格)
输出格式
满足条件的正方形农田个数。
样例数据
input
3 3
110
110
000
output
5
样例解释
边长为1的正方形农田有4块
边长为2的正方形农田有1块
合起来就是5块
数据规模与约定
时间限制: 1 s 1 \text {s} 1s
空间限制: 256 MB 256 \text {MB} 256MB
本题我们会发现,以
[
i
,
j
]
[i,j]
[i,j]为右下角的最大合法正方形边长和以
[
i
,
j
]
[i,j]
[i,j]为右下角的不同正方形个数是相同的。
而大正方形肯定是由小正方形推出来的。
得到了
a
s
d
[
i
]
[
j
]
=
m
i
n
(
a
s
d
[
i
−
1
]
[
j
]
,
m
i
n
(
a
s
d
[
i
]
[
j
−
1
]
,
a
s
d
[
i
−
1
]
[
j
−
1
]
)
)
+
1
asd[i][j]=min(asd[i-1][j],min(asd[i][j-1],asd[i-1][j-1]))+1
asd[i][j]=min(asd[i−1][j],min(asd[i][j−1],asd[i−1][j−1]))+1
Code:
/// _ooOoo_
/// o8888888o
/// 88" . "88
/// (| -_- |)
/// O\ = /O
/// ____/`---'\____
/// .' \\| |// `.
/// / \\||| : |||// \
/// / _||||| -:- |||||- \
/// | | \\\ - /// | |
/// | \_| ''\---/'' | |
/// \ .-\__ `-` ___/-. /
/// ___`. .' /--.--\ `. . __
/// ."" '< `.___\_<|>_/___.' >'"".
/// | | : `- \`.;`\ _ /`;.`/ - ` : | |
/// \ \ `-. \_ __\ /__ _/ .-` / /
///======`-.____`-.___\_____/___.-`____.-'======
/// `=---='
///^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
///
/// 佛祖保佑 永无BUG
///
/// 本模块已经经过ycy开光处理,绝无可能再产生bug
///
///=============================================
/// へ /|
/// /\7 ∠_/
/// / │ / /
/// │ Z _,< / /`ヽ
/// │ ヽ / 〉
/// Y ` / /
/// ?● ? ● ??〈 /
/// () へ | \〈
/// >? ?_ ィ │ //
/// / へ / ?<| \\
/// ヽ_? (_/ │//
/// 7 |/
/// >―r ̄ ̄`?―_
#include<bits/stdc++.h>
using namespace std;
#define f1(a,b,c) for(int c=a;c<=b;c++)
#define f2(a,b,c) for(int c=a;c>=b;c--)
#define so1(a,n) sort(a+1,a+n+1,mycmp);
#define so2(a,n) sort(a+1,a+n+1);
#define ll long long
#define in cin
#define out cout
const int twx=1000+100;
char ditu[twx][twx]={};
int asd[twx][twx]={};
int n,m;
int sum;
void init()
{
in>>n>>m;
f1(1,n,i)
{
f1(1,m,j)
{
in>>ditu[i][j];
if(ditu[i][j]=='1')
{
asd[i][j]=1;
}
}
}
}
void work()
{
f1(1,n,i)
{
f1(1,m,j)
{
if(ditu[i][j]=='0')
{
continue ;
}
asd[i][j]=min(asd[i-1][j],min(asd[i][j-1],asd[i-1][j-1]))+1;
sum+=asd[i][j];
}
}
}
void print()
{
out<<sum;
}
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
init();
work();
print();
return 0;
}