DP专场。。
动态规划是运筹学的一个分支, 求解决策过程最优化的数学方法。
我们一般把动态规划简称为DP(Dynamic Programming)
 
1.动态规划的背包问题
有一个容量为m的背包,有n个物品,每一个物品i的重量为w[i],价值为v[i]。
要求选择一些物品放入背包中,每种物品只能最多使用一次,使得在不超重的情况下让背包中所有物品价值总和最大。
正常向解法:设状态数组f[i][j]为把前i个物品放入一个容量为j的背包中所能获得的最大价值(以下同设),则状态转移方程为:
f[i][j] = max(f[i-1][j],f[i-1][j-w[i]]+v[i])
可优化至一维数组,令j从大到小枚举。
f[j] = max(f[j],f[j-w[i]]+v[i])
1.1完全背包
仍然是容量为m的背包,n个物品,每一个物品i的重量为w[i],价值为v[i]。
要求选择一些物品放入背包中,并且每一件物品可以选无限多次,使得在不超重的情况下让背包中所有物品价值总和最大。
f[i][j] = max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i])
第一维也可以优化。
f[j] = max(f[j],f[j-k*w[i]]+k*v[i])
1.2多重背包
依旧是容量为m的背包,n个物品,每一个物品i的重量为w[i],价值为v[i]。
要求选择一些物品放入背包中,并且每一件物品的数量为s[i],使得在不超重的情况下让背包中所有物品价值总和最大。
它相对于完全背包的不同之处是每种物品可选的数量有一个上限。
f[i][j] = max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i]),k*v[i] ≤ j, k ≤ s[i]
它的时间复杂度是O(m*Sigma(s[i])),而且看起来并不能像之前那样优化了。。
实际上它是可以优化的。
用「二进制拆分」即可。
对每种物品,我们将它转化成若干个物品,其中每个物品有一个系数,这个物品的费用和价值均是原费用和原价值乘这个系数。令这些系数分别为1,2,4,...,2^k-1,s[i]-(2^k)+1,且k是满足s[i]-(2^k)+1>0的最大整数,例如,如果s[i] = 11,就将这种物品分成系数分别为1,2,4,4这四个物品。
这些物品的系数和能拼出[0,s[i]]中的任意一个整数。这样一来我们就把一个n个物品的多重背包问题转化成了一个Sigma(log s[i])个物品的01背包问题了。
时间复杂度为O(m*Sigma(log s[i]))
实际上,这还能再优化。。。
用单调队列。
单调队列形似一个普通队列,但其中的点按顺序存在某种单调性(我记得我Day1的整理上提到过)。
不同于优先队列,他俩内部结构不太一样。
注意转移式:f[i][j] = max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i]),这时候k的范围已经变成了k≤min(s[i],ceil(j/w[i])).
对于某一个i而言,因为j = k*w[i]+s的点只能转移到j = (k+1)*w[i]+s , (k+2)*w[i]+s...,所以我们可以按对w[i]取模的值对j进行分类。
把模w[i]相等的点放在一起进行转移,转移后的点会放入单调队列中。我们发现由于队列中的点j是从小到大的,所以会存在转移某一点k,队首点head已经不能作为转移点,即(k-head)/w[i]>s[i]。
显然,该点在以后的转移中也不会成为转移点,所以直接弹出队首即可,直到队首可以作为转移点。转移时选择队首的点作为转移点, 因为这个单调队列是保证转移式是单调递减的。
将当前点k压入队列时,需要判断队尾点tail与k的优劣。如果tail作为转移点不比k更优,显然tail在以后的转移也不会作为转移点了,弹出tail,直至tail比k更优。
对于每个i来说,单次转移是O(m)的,所以时间复杂度是O(nm).
 
2.动态规划的线性(序列)类(一维)问题
经典例题:一个长度为n的数列,第i个数为a[i],要求选择连续且非空的一段,使这一段中所有数的和加起来最大,输出这个最大和。
正在看这篇随笔的读者朋友,您应该会显然想到O(n^2)的做法。
我们考虑DP。令f[i]表示以i为结尾的最大和子段,那么转移只有:和前面的最大子段和连起来,或者自己单独成为一段。
f[i] = a[i] +max(f[i-1],0)
答案是max(f[i]),i∈[1,n]。
时间复杂度O(n)
 
2.1 LIS
有一个长度为n的序列,第i个数为a[i],求最长上升子序列的长度。
最长上升子序列的英文名叫Longest Increasing Subsequence,简称LIS。
正在看这篇随笔的读者朋友,您应该会显然想到O(n^2)的做法。
我们重点讨论O(nlogn)的做法。
我们设一个数组g[i],它表示长度为i的LIS中,作为结尾最小的数。
如果原数组a[i] = {6,7,1,5,4,3,4,2,8},那么g数组的变化应该是:
1:g [1] = 6
2:g [1] = 6; g [2] = 7
3:g [1] = 1; g [2] = 7
4:g [1] = 1; g [2] = 5
5:g [1] = 1; g [2] = 4
6:g [1] = 1; g [2] = 3
7:g [1] = 1; g [2] = 3; g [3] = 4
8:g [1] = 1; g [2] = 2; g [3] = 4
9:g [1] = 1; g [2] = 2; g [3] = 3; g [4] = 8
容易看出,每次g数组只会修改一个值或者加入一个值。而且无论如何,g数组总是保持单调递增。
显然,当LIS的长度相等时,结尾的数肯定是越小越好,这样才有更大的机会与后面的数相接,才能生成更长的LIS。这样一来,g数组一定是单调递增的,因为g数组不会存一个较长的LIS,使得它的结尾数比较短的LIS还小。每次考虑以i结尾的上升子序列时,我们只要在g数组中找到最大的的小于a[i]的位置j,令g[j+1] = len即可。
由于g单调,所以可以用二分查找来找到位置j,单次转移复杂度为O(logn)。
最终复杂度为O(nlogn).
 
例:有两个序列a,b,长度分别为n,m,求它们的LCS。
LCS指最长公共子序列。表示从两个序列中各自选出长度相等的子序列,这两个子序列的数对应相等。
设f[i][j]表示两个序列分别dp到第i,j个数,公共子序列的最大长度。
则有
f[i][j] = max(f[i-1][j],f[i][j-1]) (a[i]!=b[j])
f[i][j] = f[i-1][j-1] + 1(a[i] == b[j])
时间复杂度O(n^2)。
 
例2:在上例条件不变的情况下,求LCIS
LCIS指最长公共上升子序列。表示从两个序列中各自选出长度相等的上升子序列,这两个子序列的数对应相等。
设f[i][j]表示两序列分别以i,j结尾的LCIS。
一个简单的想法是
f[i][j] = max(f[k][l]+1) (k < i , l < j, a[k] < a[i], b[l] < b[j], a[i]==b[j])
我们先枚举i,再枚举j,当a[i] != b[j] 时f[i][j] = 0。但当a[i] >b[j] 时,在同一个i下,所有f[k][j] (k<i)是可以作为转移点转移后面的状态f[i][j'],因为存在方案的f[k][j]一定保证a[k]<a[i],b[j]<b[j']。
我们可以开一个数组来记录第二维为j且第一维小于i的f的最大值,不过第一维是可以被优化掉的。
设计状态f[i]表示b序列以b[i]结尾的LCIS,具体实现:
 for (int i=;i<=n;i++){
int k= ;
for (int j=;j<=m;j++)
if (a[i] == b[j])
f[j] = max(f[j],k+);
else
if (a[i] > b[j])
k = max(k,f[j]);
}
空间复杂度降为O(m)。
 
3.树形(树上)DP
顾名思义,是在树上所做的动态规划,其基础是树具有严格的层数关系而不会重复的特性。
一般来说树形DP是处理子树的信息以及其相互关系来进行转移。其状态一般会表示成以i为根的子树的DP值。
 
3.1邻接表存树
在之前的知识点整理上提前说了,这里只提供代码。
 struct Edge_tree{
int u,v,w;
int next; };
Edge_tree edge[maxn];
int cnt = ;
int first[maxn];
void add_edge(int from,int to,int dis){
edge[++cnt].u = from;
edge[cnt].v = to;
edge[cnt].w = dis;
edge[cnt].next = fisrt[from];
first[from] =cnt; edge[++cnt].v = from;
edge[cnt].u = to;
edge[cnt].w = dis;
edge[cnt].next = first[to];
first[to] = cnt; } void dfs_tree(int x,int fa){
//cout << x << " ";
for (int i = first[x];i!=;i = edge[i].next)
if (edge[i].v != fa)
dfs_tree(edge[i].v,x);
}
 
3.2最大连通子树
有一个n个节点的树,每个点有点权a[i],求一棵连通子树使点权之和最大。
(n ≤ 10^5,|a[i]| ≤ 10^9)
f[u] = a[u] + Sigma(max(f[v],0))
最终答案是max(f[i]), i∈[1,n]
 
3.3树的直径
有一个n个节点的树,每条边有边权w[i],求一条路径使得它的所有边权和最大。
这条路径就叫做这棵树的直径。
(n ≤ 10^5,|a[i]| ≤ 10^9)
如果边权保证非负,我们可以用两遍bfs求得直径。具体做法是第一次随便选一个点,bfs求得与它距离最远的点x,再从x出发bfs求得与x距离最远的点y,x与y之间的距离就是树的直径。
若边权存在负数,则不能使用这个方法。
设f[u]表示在u为根的子树中,存在点u的最大路径边权和(u可以为端点也可以为中间的点)
设g[u]表示在u为根的子树中,存在点u的最大路径边权和(u只能为端点,即由u发出的一条链)
f[u] = max( firstmax{g[v] + w(u,v)}, 0) + max(secondmax{g[v] + w(u,v)},0)
g[u] = max{g[v] + w (u,v)}
 
4.区间DP以及其他DP
 
4.1区间DP
顾名思义,是在一个区间上进行的一系列动态规划,一般考虑对于每段区间,它们的最优值都是由两段或者更多段的小区间点的最优值得到,是分治思想的一种应用。
一般定义状态f[i][j]表示从区间i到j的DP最优值,转移时枚举中间点k,从f[i][k],f[k+1][j]来进行合并
例题:合并石子
n堆石子排成一列,每堆石子有一个重量w[i],每次可以合并相邻的两堆石子,一次合并的代价为二者重量之和。问怎样安排合并顺序使得代价最小。
n,w ≤100
解:用s[i]表示石子的前缀和,有
f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])
时间复杂度O(n^3)。
 
变式:n堆石子排成一个圆,其他条件不变。
解:在后面加上一条排列相同的石子堆,扩展到2n-1个石子。依然是用s[i]表示w[i]的前缀和。有
f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])
时间复杂度O(n^3),最终答案是min(f[i][i+n-1]),其中1 ≤ i ≤ n。
 
变式:使最终代价最大, 其他条件不变
解:贪心的来想,我们肯定是让每个石子重量都尽可能多的被计算,也就是说每次只合并一个石子进来应该是一个最优的策略。
f[i][j] = max(f[i+1][j],f[i][j-1])+s[j]-s[i-1]
时间复杂度O(n^2)。
 
4.2棋盘DP
非常好想的一类DP,在一个二维网格(地图)上做DP。
一般设f[i][j]表示走到(i,j)位置上的最优值。
 
4.3DAG上的DP
给定一个DAG(有向无环图),要求统计一些信息。
DAG是一个比较规则的结构,我们可以对这些点进行拓扑排序后再DP。
 
4.4状压DP
(GTMDNOIP2016D2T3
状态压缩DP,通过二进制位上的0/1表示状态,一般用于要记录一段较小规模的状态且该状态包含信息较多的问题。
就目前来说,在NOIP史上只有去年考到了。
谁知道今年还会考什么奇怪的东西呢……
 
4.5概率期望DP
(GTMDNOIP2016D1T3
这种题目会丧心病狂的让你求某一事件的期望或者概率。。
并不想写这类丧心病狂的东西。
 
4.6数位DP
(这已经超纲了吧。
这种题目一般会让你统计某一区间内的与数位或与数相关的信息,但由于区间较大而没法暴力求解,所以要在数位上进行DP。
就目前来说,没考过,谁知道今年考不考。。
 
5.相关优化
5.1前缀和优化
一般用于转移点的取值是一段连续的区间,做一下前缀和可以把转移从O(n)降至O(1)。
 
5.2滚动数组优化
这个还挺常见的。一般用于二维及以上的DP。如果某一维i的dp值只与i-1的dp值有关那么我们不用存这一维全部的情况,用0/1状态来存储当前状态和转移点状态就可以了,这样会降低空间复杂度。
 
5.3单调性优化
利用对某一属性的单调性来加速转移,体现在减少转移点数量和快速查询转移点的情况,常见的是单调队列, 二分查找等。
 
5.4数据结构优化
即利用一些数据结构来进行优化。NOIP阶段常用堆。
 
5.5其他优化
减少冗余状态
利用数据结构的特殊性质
女装
 
 
 

夏令营讲课内容整理 Day 5.的更多相关文章

  1. 夏令营讲课内容整理 Day 7.

    Day7是夏令营的最后一天,这一天主要讲了骗分技巧和往年经典的一些NOIP试题以及比赛策略. 这天有个小插曲,上午的day7T3是一道和树有关的题,我是想破脑袋也想不出来,正解写不出来就写暴力吧,暴力 ...

  2. 夏令营讲课内容整理 Day 3.

    本日主要内容是树与图.   1.树 树的性质 树的遍历 树的LCA 树上前缀和   树的基本性质: 对于一棵有n个节点的树,必定有n-1条边.任意两个点之间的路径是唯一确定的.   回到题目上,如果题 ...

  3. 夏令营讲课内容整理Day 0.

    今年没有发纸质讲义是最气的.还好我留了点课件. 第一次用这个估计也不怎么会用,但尝试一下新事物总是好的. 前四天gty哥哥讲的内容和去年差不多,后三天zhn大佬讲的内容有点难,努力去理解吧. 毕竟知识 ...

  4. 夏令营讲课内容整理 Day 6 Part 3.

    第三部分主要讲的是倍增思想及其应用. 在Day3的整理中,我简要提到了倍增思想,我们来回顾一下. 倍增是根据已经得到的信息,将考虑的范围扩大一倍,从而加速操作的一种思想,它在变化规则相同的情况下,加速 ...

  5. 夏令营讲课内容整理 Day 6 Part 2.

    Day 6的第二部分,数论 数论是纯粹数学的分支之一,主要研究整数的性质   1.一些符号: a mod b 代表a除以b得到的余数 a|b a是b的约数 floor(x) 代表x的下取整,即小于等于 ...

  6. 夏令营讲课内容整理 Day 6 Part 1.

    Day6讲了三个大部分的内容. 1.STL 2.初等数论 3.倍增   Part1主要与STL有关. 1.概述 STL的英文全名叫Standard Template Library,翻译成中文就叫标准 ...

  7. 夏令营讲课内容整理 Day 4.

    本日主要内容就是搜索(打暴力 搜索可以说是OIer必会的算法,同时也是OI系列赛事常考的算法之一. 有很多的题目都可以通过暴力搜索拿到部分分,而在暴力搜索的基础上再加一些剪枝优化, 就有可能会拿到更多 ...

  8. 夏令营讲课内容整理 Day 2.

    本日主要内容是并查集和堆. 并查集 并查集是一种树型的数据结构,通常用来处理不同集合间的元素之间的合并与查找问题.一个并查集支持三个基本功能:合并.查找和判断.举一个通俗的例子,我和lhz认识,lhz ...

  9. 夏令营讲课内容整理Day 1.

    主要内容是栈和队列. 1.  栈 运算受到限制的线性表.只允许从一端进行插入和删除等操作.这一端便是栈顶,另一端便是栈底. 其实可以把栈想象层任何有底无盖的柱状的容器...毕竟栈满足后进先出的特性.计 ...

随机推荐

  1. hdu_1286找新朋友(欧拉定理)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1286 找新朋友 Time Limit: 2000/1000 MS (Java/Others)    M ...

  2. jquery 和 mui 上拉加载

    jquery: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <m ...

  3. 算法--链表的K逆序问题

    转载请标明出处http://www.cnblogs.com/haozhengfei/p/9e6f4dda3138cf9fab17f996ec85b624.html 链表的K逆序问题     链表的k逆 ...

  4. Oracle_view视图

    Oracle_view视图 视图view --视图:view --查询班级信息并统计各班的人数 select * from stu; select * from clazz;     select c ...

  5. php备份数据库类分享

    本文实例讲述了php实现MySQL数据库备份类.分享给大家供大家参考.具体分析如下:这是一个非常简单的利用php来备份mysql数据库的类文件,我们只要简单的配置好连接地址用户名与数据库即可   ph ...

  6. 积分图实现均值滤波的CUDA代码

    没想到我2010年买的笔记本显卡GT330M 竟然还能跑CUDA,果断小试了一把,环境为CUDA6.5+VS2012,写了一个积分图实现均值滤波.类似于OpenCV的blur()函数. 使用lena. ...

  7. Objective-C基础教程学习笔记(附录)从Java转向Objective-C

    Java接口与Objective- C正式协议类似,因为它们都需要实现一组方法.Java具有抽象类,但Objective-C没有.Java具有类变量,但在Objective-C中, 可以使用文件范围内 ...

  8. hadoop问题: bin/hadoop fs -ls ls: `.': No such file or directory

    问题描述:bin/hadoop fs -ls ls: `.': No such file or directory 问题分析:版本问题,用法不同 https://stackoverflow.com/q ...

  9. Python调用外部程序——os.system()和subprocess.call

    通过os.system函数调用其他程序 预备知识:cmd中打开和关闭程序 cmd中打开程序 a.打开系统自带程序 系统自带的程序的路径一般都已加入环境变量之中,只需在cmd窗口中直接输入程序名称即可. ...

  10. 记录linux tty的一次软锁排查

    本过程参照了某大侠的https://github.com/w-simon/debug/blob/master/tty_lock_cause_sytemd_hung , 当第二次出现的时候,还是排查了一 ...