2017.10.28闵神讲课DAY1

OMG 我辛辛苦苦打的笔记被吃掉了 只能转载我们涵神的了 反正我们写的差不多

附上涵神原文:传送门

背景

MZX大佬来这边的路真是艰辛啊!说好九点的飞机,十一点就能到,结果晚点到十点。到早上我们才知道他晚点到了一点,四点才到的海亮教育园。所以上午就看了万能的区间DP。然后下午终于出现在了讲台上面……
12:24大佬开始卖弱
12:30终于开始讲了

搜索到DP的过渡

搜索的艰辛

搜索的时间复杂度是指数级的。我们如果把dp的题目用搜索做的话简直会爆炸。例如最经典的01背包,如果不会dp,我们只能用dfs来枚举每个物品选不选,选的加进vector里面。好一点的dfs可以只记录已经选的物品质量和价值;最后加可行性剪枝和最优性剪枝,可以稍微好一点。

记忆化出现了

搜索的时间复杂度越来越不能征服人类日益增长的需求了!因为人们的cpu不能发展的那么快(摩尔定律都学过吧)。所以这时候一个救世主出现了——记忆化搜索。
dfs的最优性剪枝:记录下前i个物品总重量为w时候的最大价值和,或者价值为v时候的最小总重量。(记忆化搜索)Dfs可以带三个参数:dfs(物品件数,物品总重,物品总价值),类似地我们可以加一个f数组,来记录这种状态:f[i][w]=v来代表前i个物品质量w时候的最大价值v。本质就是我们在搜索的时候只记录对后面的选择有影响的状态,以及该状态下的最优值。这种方法适用于很多0/1选择的问题,比如lis。记录xx状态时候的最优值这种做法其实就是dp,只不过我们把指数级的算法剪成了多项式。Dp的关键在于写出状态转移方程,就像这样 f[i]=max/min{}
Dp的思考方式:先用dfs思考一下,再用参数写出状态数组。最后写出状态转移方程。

DP的基础

DP就是这样应运而生的。

DP的实现方法

Dp有两种实现方法:递推和记忆化搜索,后面就体现出了这个搜索——“剪枝”算法的本质。

例题

题面

给定一个RxC(R,C≤500)的滑雪场,每个格子都有高度,每个格子只能滑向(四联通)相邻的比它低的格子,求最大的滑行长度(可以任意确定起点).

分析

很明显,如果搜索,那么对选择有影响的状态只有当前位于哪个格子,所以就可以设置状态为当前格子,最优值就是以这个格子为起点的最大滑行长度。
一个格子的最优值依赖于哪些格子呢?比它低的。
所以我们可以dfs递归搜索,加上记忆化就能ac了。说实话这道题目用dp还不如写dfs呢。。。

再谈LCS

LCS——最长公共子序列。大家都会把。这里会介绍一种具有特殊性质的LCS。

题面

给定两个长度为5n的序列,1…n每个数字出现5次,求最长公共子序列,n≤1e5。

回忆一般的LCS

方法一:1、dfs法:不断分解组合。
2、DP法:从规模小的子问题推到总问题。
我们需要枚举两个串的每一位,在这道题目中时间复杂度是 On2 ,明显超时。
具体做法是这样:
如果a[i-1]=b[i-1]那么
F[i][j]=max(f[i][j],f[i1][j1]+1);
如果不相等,那么
F[i][j]=max(f[i1][j],f[i][j1]);

code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int num=0;
    char c=getchar();
    for(;c<'0'||c>'9';c=getchar());
    for(;c>='0'&&c<='9';c=getchar())num=num*10+c-'0';
    return num;
}
int n,m,a[302000],b[302000],f[5001][5001];
int main()
{
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
    a[i]=read();
    for(int i=1;i<=m;i++)
    b[i]=read();
    for(int i=2;i<=n+1;i++)
    for(int j=2;j<=m+1;j++)
    {
        f[i][j]=max(f[i-1][j],f[i][j-1]);
        if(a[i-1]==b[j-1])
        f[i][j]=max(f[i][j],f[i-1][j-1]+1);
    }
    printf("%d",f[n+1][m+1]);
    return 0;
}
正解算法

但是恭喜,只能拿部分分!!!o(╥﹏╥)o许多同学内心是崩溃的。这道题目我们需要再观察一下这道题目。这道题目有一个特殊性质:因为这道题目每个数会且只会出现5次。所以我们发现上面code的那个判断两个是否相等的if语句值为真的总共会有 5×5×n 。我们可以变换一下思路。为啥要把整个f数组都算出来呢?我们如果把f数组画出来。我们会发现,是一张 5n×5n 的表格,每行只有5个元素是相互匹配的。我们可以只填匹配上的点。时间复杂度就降成了 O(n) !!多么美妙!但是我们发现并没有一个可以直接填点的方法。我们可以借用一下数据结构这个好玩意儿。
这里我们采用线段树,每次查询的时间复杂度是 O(logn) 那么总的时间复杂度是 O(nlogn)
我们每填完一行我们都要修改一下线段树。让每一个结点j代表以j的横坐标和纵坐标为边的矩阵最大值。然后填值的时候就利用线段树结点的值来进行计算和判断。

code

【待补全】

DP的优化

DP的时间复杂度一般都是二次级别的,有时候数据超过10000就可能会超时。所以我们需要进行一些有的没的的优化。

单调性优化

在很多时候,dp的决策往往是具有单调性的。我们来看个例题:

例题

题面

超级教主
题目描述
LHX教主很能跳,因为Orz他的人太多了。教主跳需要消耗能量,每跳1米就会消耗1点能量,如果教主有很多能量就能跳很高。 教主为了收集能量,来到了一个神秘的地方,这个地方凡人是进不来的。在这里,教主的正上方每100米处就有一个能量球(也就是这些能量球位于海拔100,200,300……米处),每个能量球所能提供的能量是不同的,一共有N个能量球(也就是最后一个能量球在N×100米处)。教主为了想收集能量,想跳着吃完所有的能量球。教主可以自由控制他每次跳的高度,接着他跳起把这个高度以下的能量球都吃了,他便能获得能量球内的能量,接着吃到的能量球消失。教主不会轻功,教主不会二段跳,所以教主不能因新吃到的能量而变化此次跳跃的高度。并且教主还是生活在地球上的,所以教主每次跳完都会掉下来。 问教主若要吃完所有的能量球,最多还能保留多少能量。
输入格式
第1行包含两个正整数N,M,表示了能量球的个数和LHX教主的初始能量。
第2行包含N个非负整数,从左到右第I个数字依次从下向上描述了位于I×100米位置能量球包含的能量,整数之间用空格隔开。
输出格式
仅包括一个非负整数,为教主吃完所有能量球后最多保留的能量。
样例数据
input
3 200
200 200 200
output
400
【样例说明】 第1次跳100米,得到200能量,消耗100能量,所以落地后拥有300能量。 第2次跳300米,吃到剩下的第3棵能量球,消耗拥有的300能量,得到400能量。 若第1次跳200米,第2次跳300米,最后剩余300能量。
数据规模与约定
【数据规模】 对于10%的数据,有N≤10;   对于20%的数据,有N≤100; 对于40%的数据,有N≤1000; 对于70%的数据,有N≤100000; 对于100%的数据,有N≤2000000。 保证对于所有数据,教主都能吃到所有的能量球,并且能量球包含的能量之和不超过2^31-1。

分析

我们先考虑用dfs做,明显会有:void dfs(int l,int E)
并且来个记忆化f[i]代表I*100处的最小花费能量。总能量是一定的,我们止血药最小化跳的高度总和就可以了,也就是存在 f[i]=min{f[j]+100i} 我们把所有不含j的项提出来 =min{f[j]}(E+W[j]f[j]100i)+100i
(ps:W[j]表示w的前缀和)
但是这道题目我们不需要使用单调队列,止血药记录满足上述()中的条件的最小的j就可以了。代码实现:

int j=0;
for(int i=1;i<=n;i++)
{
while(E+W[j]-f[j]<100&&j<i)
j++
if(i==j)
{
//输出不可能的情况。
}
F[i]=f[j]+100*i;
}
code

【待补全】

例题

题面

取货 题目描述
某公司估计市场在第i个月对某产品的需求量为Ui,已知在第i月该产品的订货单价为di,上个月月底未销完的单位产品要付存贮费用m,假定第一月月初的库存量为零,第n月月底的库存量也为零。
问如何安排这n个月订购计划,才能使成本最低?每月月初订购,订购后产品立即到货,进库并供应市场,于当月被售掉则不必付存贮费。假设仓库容量为S。
输入格式 第1行:n,m,S (0<=n<=50,0<=m<=10,0<=S<=10000) 第2行:U1,U2,…,Ui,…,Un
(0<=Ui<=10000) 第3行:d1,d2,…,di,…,dn (0<=di<=100) 输出格式 只有1行,一个整数,代表最低成本
样例数据 input 3 1 1000 2 4 8 1 2 4 output 34

分析

这道题目明显是一道dp题目。我们令f[i][j]表示第i个月存了j件货物的总代价。
所以有状态转移方程:
f[i][j]=minf[i1][k]+k×m+(u[i]+jk)×w[i](ku[i]+j)=minf[i1][k]+k×(mw[i])+(u[i]+j)×w[i]
我们把大括号内的表达式定义为V(k)。
那么如果V(y)比V(x)更优而且x比y更早不合法,那么x这个决策在y加入中就失去了存在的意义。x生命失去了希望。

code

【待补全】

斜率优化

斜率优化就是把一些决策通过描点在一个二维平面上画出直线,通过斜率的单调性来解决。

例题

题目描述
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。
从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。
每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。
输入格式
第一行是N(1<=N<=50000)。 第二行是S(0<=S<=50)。 下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。
输出格式
一个数,最小的总费用。
样例数据
input
5
1
1 3
3 2
4 3
2 3
1 4
output
153

分析

【待补全】

code

【待补全】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值