精华内容
下载资源
问答
  • 淄博雷利seo优化团队小雷在此就围绕seo是什么意思?网站seo优化的重要性为话题,来说一下网站建成之后做seo优化的必要性! seo是什么意思?  SEO(Search Engine Optimization),汉译为搜索引擎优化。SEO的主要...

    seo是什么意思?这是建站新手在初入网站推广行列的时候,不得不回答的问题。淄博雷利seo优化团队小雷在此就围绕seo是什么意思?网站seo优化的重要性为话题,来说一下网站建成之后做seo优化的必要性!

    seo是什么意思?

           SEO(Search Engine Optimization),汉译为搜索引擎优化。SEO的主要工作是通过了解各类搜索引擎如何抓取互联网页面、如何进行索引以及如何确定其对某一特定关键词的搜索结果排名等技术,来对网页进行相关的优化,使其提高搜索引擎排名,从而提高网站访问量,最终提升网站的销售能力或宣传能力的技术。


    网站为什么做seo优化?

           SEO目的理解是:使网站更适合搜索引擎的检索,为网站提供生态式的自我营销解决方案,让网站在行业内占据领先地位,从而获得品牌收益。

           使网站更适合搜索引擎的检索原则又被称为对搜索引擎友好,对搜索引擎友好不仅能够提高SEO的效果,还能提高用户体验,因为这样会使搜索引擎中显示的网站相关信息对用户来说更有吸引力。为了说明什么是网站对搜索引擎友好,我们不妨看看对搜索引擎不友好的网站有哪些特征:
    1、网页中大量采用图片或者Flash等富媒体(Rich Media)形式,没有可以检索的文本信息,而SEO最基本的就是文章SEO和图片SEO;
    2、网页没有标题,或者标题中没有包含有效的关键词;
    3、网页正文中有效关键词比较少(一般小于整文章2%~8%);
    4、网站导航系统让搜索引擎“看不懂”;
    5、大量动态网页让搜索引擎无法检索;
    6、没有被其他已经被搜索引擎收录的网站提供的链接;
    7、网站中充斥大量欺骗搜索引擎的垃圾信息,如“过渡页”、“桥页”、颜色与背景色相同的文字;
    8、网站中缺少原创的内容,完全照搬硬抄别人的内容等。

    展开全文
  • 英文似乎是 receding horizon control / model predictive control ? 滚动时域优化/滚动时域控制 在一些文献里遇到这个词,似乎是一种动态决策过程? 查了半天也没整明白是啥意思
  • 动态规划(DP)优化之斜率优化讲解

    千次阅读 2017-06-01 16:23:51
    “DP的斜率优化——对不必要的状态量进行抛弃,对不优的状态量进行搁置,使得在常数时间内找到最优解成为可能。斜率优化依靠的是数形结合的思想,通过将每个阶段和状态的答案反映在坐标系上寻找解答的单调性,来在一...

    “DP的斜率优化——对不必要的状态量进行抛弃,对不优的状态量进行搁置,使得在常数时间内找到最优解成为可能。斜率优化依靠的是数形结合的思想,通过将每个阶段和状态的答案反映在坐标系上寻找解答的单调性,来在一个单调的答案(下标)队列中O(1)得到最优解。”

    在呈交给教练的总结里,我这样写到。确实,集训了几天,对斜率优化感受颇多,就此写一下自己的理解。

    斜率优化的思想在2004年国家集训队员周源大神撰写的《浅谈数形结合思想在信息学竞赛中的应用》中有详细阐述,想要看原文的同学可以戳这里:
    https://wenku.baidu.com/view/b97cd22d0066f5335a8121a3.html

    ”一些试题中繁杂的代数关系身后往往隐藏着丰富的几何背景,而借助背景图形的性质,可以使那些原本复杂的数量关系和抽象的概念,显得直观,从而找到设计算法的捷径。”—— 周源《浅谈数形结合思想在信息学竞赛中的应用》

    斜率优化的核心即为数形结合,具体来说,就是以DP方程为基础,通过变形来使得原方程形如一个函数解析式,再通过建立坐标系的方式,将每一个DP方程代表的状态表示在坐标系中,在确定“斜率”单调性的前提下,进行形如单调队列操作的舍解和维护操作。(不知道单调队列的同学可以戳这里:http://baike.baidu.com/link?url=LLbLXZEpfkUY06P67Um5TAojjCfOUUTv3Yc5v5ymcroEsCft8XHAM_TphojFI8YGiopeW5jVphE3VCWIRjg-pK2-F3eeI5ynp7wJ8Mz7kOB4GHrIpBhQ_7F-DgxCGzPg

    一个算法总是用于解决实际问题的,所以结合例题来说是最好的:


    Picnic Cows(HDU3045)

    题目链接:https://vjudge.net/problem/HDU-3045

    题目大意:
    给出一个有N (1<= N <=400000)个正数的序列,要求把序列分成若干组(可以打乱顺序),每组的元素个数不能小于T (1 < T <= N)。每一组的代价是每个元素与最小元素的差之和,总代价是每个组的代价之和,求总代价的最小值。

    样例输入包含:
    第一行 N
    第二行 N个数,如题意

    样例输出包含:
    第一行 最小的总代价

    分析:
    首先,审题。可以打乱序列顺序,又知道代价为组内每个元素与最小值差之和,故想到贪心,先将序列排序(用STL sort)。
    先从最简单的DP方程想起:
    容易想到:

    f[i] = min( f[j] + (a[j + 1 -> i] - Min k) ) (0 <= j < i)

    – –> f[i] = min( f[j] + sum[i] - sum[j] - a[j + 1] * ( i - j ) )

    Min k 代表序列 j + 1 -> i 内的最小值,排序后可以简化为a[j + 1]。提取相似项合并成前缀和sum。这个方程的思路就是枚举 j 不断地计算状态值更新答案。但是数据规模达到了 40000 ,这种以O(n ^ 2)为绝对上界方法明显行不通。所以接下来我们要引入“斜率”来优化。

    首先要对方程进行变形:
    f[i] = f[j] + sum[i] - sum[j] - a[j + 1] * ( i - j )
    – –> f[i] = (f[j] - sum[j] + a[j + 1] * j) - i * a[j + 1] + sum[i]
    (此步将只由i决定的量与只由j决定的量分开)
    由于 sum[i] 在当前枚举到 i 的状态下是一个不变量,所以在分析时可以忽略(因为对决策优不优没有影响)(当然写的时候肯定不能忽略)

    令 i = k
    a[j + 1] = x
    f[j] - sum[j] + a[j + 1] * j = y
    f[i] = b
    原方程变为
    – –> b = y - k * x
    移项
    – –> y = k * x + b

    是不是很眼熟? 没错,这就是直线的解析式。观察这个式子,我们可以发现,当我们吧许许多多的答案放在坐标系上构成点集,且枚举到 i 时,过每一个点的斜率是一样的!! 很抽象? 看图

    图1

    可以看出,我们要求的f[i]就是截距,明显,延长最右边的直线交于坐标轴可得最小值。难道只要每次提取最靠近 i 的状态就行了嘛?现实没有那么美好。

    图2

    像这样的情况,过2直线的截距明显比过3直线的截距要小, 意味着更优(在找求解最小值问题时),这种情况下我们之前的猜想便行不通。

    那怎么办呢?这时就要用到斜率优化的核心思想——维护凸包。
    何为凸包?
    不懂得同学还是戳这里:http://baike.baidu.com/link?url=OWX7haiZHtuKD2hjbEBVouUGxKXIMvXDnKra0xDhxuz9zGttTg_JoRwmUcbrGD9Xp2BnbCJDF8BblaQffDBvblg0xNqgIOXCVZpQ7ZNBkWG

    其实我们要维护的凸包与这个凸包并无关系,只是在图中长得像罢了。
    那为什么要维护凸包呢?
    还要看图:
    图3

    这就是一个下凸包,由图可见,最前面的那个点的截距最小,也诠释了维护凸包的真正含义(想一想优先队列,是不是队首最优?)。那还是有人会提出疑问,为什么非要维护这样的凸包呢? 答案就是,f[i]明显是递增的(相比于f[j] 加上一个代价),所以会在图中自然而然地显现出 Y 随着 X增加而增加的情况,呈现出凸包的模样。

    可能这个过程比较晦涩难懂,没懂的同学可以多看几遍。

    现在我们回到对 的分析

    现在假设我们正在枚举 j 来更新答案,有一个数 k, j < k < i
    再来假设 k 比 j 优(之所以要假设正是要推出具体情况方便舍解)

    则有

    f[k] + sum[i] - sum[k] - a[k + 1] * (i - k) <=
    f[j] + sum[i] - sum[j] - a[j + 1] * (i - j) (k > j)

    移项消项得
    f[k] - sum[k] + a[k+ 1] * k - (f[j] - sum[j] + a[j + 1] * j) <=
    i * (a[k + 1] - a[j+ 1])

    将只与 i 有关的元素留下,剩下的除过去, 得到

    f[k] - sum[k] + a[k+ 1] * k - (f[j] - sum[j] + a[j + 1] * j) /
    a[k + 1] - a[j + 1] <= i
    (这里注意判断除数是否为负, 要变号,当然这里排序过后对于 k > j a[k] 肯定大于 a[j])

    这个式子什么意思呢?我用人类的语言解释一下。
    设 Xi = a[i], Yi = f[i] - sum[i] + a[i + 1] * i
    那么原式即为如下形式:

    (Yk - Yj) / (Xk - Xj) <= i

    意思就是当有k 和 j 满足 j < k 的前提下 满足此不等式
    则证明 j 没有 k 优

    而这个式子的左边数学意义是斜率, 而右边是一个递增的变量, 所以递增的 i 会淘汰队列里的元素, 而为了高效的淘汰, 我们会(在这道题里)选用斜率递增的单调队列,也就是上凸包。(再看看前面的图,是不是斜率在递增)

    我们还可以从另一个角度理解维护上凸包的理由

    仔细观察下面的图:

    一开始,1 号点的截距比2号点更优

    这里写图片描述

    随着斜率的变化,两个点的截距变得一样了

    然后,斜率接着变化,1号点开始没有2号点优了,所以要舍弃

    这里写图片描述

    后面的过程,3号点会渐渐超过2号点,并淘汰掉2号点

    这里写图片描述

    分析整个过程,最优点依次是 1 -> 2 -> 3,满足单调的要求

    但是如果是一个上凸包会怎样呢?

    这里只给出最终图(有兴趣的同学可以自己推一推),可以预见的是,在1赶超2前,3先赶超了2就破坏了顺序,因此不行

    这里写图片描述

    思路大概是清晰了,现在只剩下代码实现方面的问题了

    下面就看看单调队列的操作

    先将推出的X, Y用函数表示方便计算:
    X:

    dnt X( int i, int j )
    {
        return a[j + 1] - a[i + 1];
    }

    (dnt 是 typedef 的 long long)

    Y:

    dnt Y( int i, int j )
    {
        return f[j] - sum[j] + j * a[j + 1] - (f[i] - sum[i] + i * a[i + 1]);
    }

    处理队首:

    for(; h + 1 < t && Y(Q[h + 1], Q[h + 2]) <= i * X(Q[h + 1], Q[h + 2]); h++);

    从队尾维护单调性:
    (这里是一个下凸包所以两点之间的斜率要递增,即 斜率(1, 2) < 斜率(2, 3), 前一个斜率比后一个小)

    for(; h + 1 < t && Y(Q[t - 1], Q[t]) * X(Q[t], cur) >= X(Q[t - 1], Q[t]) * Y(Q[t], cur); t--);

    (注意,要把除法写成乘的形式,不然精度可能会出问题)

    斜率优化部分已经完结(说起来挺复杂其实代码很短),接下来就放出AC代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    typedef long long dnt;
    
    int n, T, Q[405005];
    dnt sum[405005], f[405005], a[405005];
    
    dnt Y( int i, int j )
    {
        return f[j] - sum[j] + j * a[j + 1] - (f[i] - sum[i] + i * a[i + 1]);
    }
    
    dnt X( int i, int j )
    {
        return a[j + 1] - a[i + 1];
    }
    
    dnt DP( int i, int j )
    {
        return f[j] + (sum[i] - sum[j]) - (i - j) * a[j + 1];
    }
    
    inline dnt R()
    {
        static char ch;
        register dnt res, T = 1;
        while( ( ch = getchar() ) < '0'  || ch > '9' )if( ch == '-' )T = -1; 
            res = ch - 48;
        while( ( ch = getchar() ) <= '9' && ch >= '0')
            res = res * 10 + ch - 48;
        return res*T;
    }
    
    int main()
    {
        sum[0] = 0;
        while(~scanf( "%d%d", &n, &T ))
        {
            a[0] = 0, f[0] = 0;
            for(int i = 1; i <= n; i++)
                scanf( "%I64d", &a[i] );
            sort(a + 1, a + n + 1);
            for(int i = 1; i <= n; i++)
                sum[i] = sum[i - 1] + a[i];
            int h = 0, t = 0;
            Q[++t] = 0;
            for(int i = 1; i <= n; i++)
            {
                int cur = i - T + 1;
                for(; h + 1 < t && Y(Q[h + 1], Q[h + 2]) <= i * X(Q[h + 1], Q[h + 2]); h++);
                f[i] = DP(i, Q[h + 1]);
                if(cur < T) continue;
                for(; h + 1 < t && Y(Q[t - 1], Q[t]) * X(Q[t], cur) >= X(Q[t - 1], Q[t]) * Y(Q[t], cur); t--);
                Q[++t] = cur;
            }
            printf( "%I64d\n", f[n] );
        }   
        return 0;
    }
    展开全文
  • bzoj1597 土地购买 动态规划&斜率优化

    千次阅读 2016-01-09 13:33:09
    最最基础的斜率优化(这么基础的斜率优化还WA这么多发,还好意思说)。  显然如果a[i]>=a[j]且b[i]>=b[j],j是没有什么卵用的,直接去掉,那么如果将a降序排序,可以发现b一定是升序的。从而显然有方程,f[i]=min{f...

           最最基础的斜率优化(这么基础的斜率优化还WA这么多发鄙视,还好意思说)。

           显然如果a[i]>=a[j]且b[i]>=b[j],j是没有什么卵用的,直接去掉,那么如果将a降序排序,可以发现b一定是升序的。从而显然有方程,f[i]=min{f[j]+a[j+1]*b[i]}。那么对于j<k有斜率s(j,k)=(f[k]-f[k])/(a[j+1]-a[k+1]),当b[i]>=s(j,k)时k比j更优。那么当s(i,j)>s(j,k),且i<j<k时,可以证明j是无用的。单调队列维护这个斜率(似乎是叫做一个上凸包?),就可以O(1)转移了。

           P·S:然而我犯了sb错误,注意到上面a[j+1],显然这个a是处理后的,即a[j]和a[j+1]都应该是符合要求的。而我一开始写的时候只是保证了j是合法的。。。结果出数据死活出不出来,比如说:

    2

    7 10

    9 2

    原来的程序会输出88

    然而在里面加一些会被舍去的解就会输出90。。o(╯□╰)o。。这怎么想得到啊。。。(果然还是万能的对拍大法)

    看来还是要先把合法队列求出来呢。。

    AC代码如下:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 100005
    #define ll long long
    using namespace std;
    
    int n,cnt,q[N]; ll f[N]; struct node{ ll x,y; }a[N];
    bool cmp(node aa,node bb){ return aa.x>bb.x || (aa.x==bb.x && aa.y>bb.y); }
    int main(){
    	scanf("%d",&n); int i;
    	for (i=1; i<=n; i++) scanf("%lld%lld",&a[i].x,&a[i].y);
    	sort(a+1,a+n+1,cmp);
    	for (i=1; i<=n; i++) if (a[i].y>a[cnt].y) a[++cnt]=a[i];
    	int head=1,tail=1; q[1]=0;
    	for (i=1; i<=cnt; i++){
    		while (head<tail && a[i].y*(a[q[head]+1].x-a[q[head+1]+1].x)>=f[q[head+1]]-f[q[head]]) head++;
    		f[i]=f[q[head]]+a[q[head]+1].x*a[i].y;
    		while (head<tail && (f[q[tail]]-f[q[tail-1]])*(a[q[tail]+1].x-a[i+1].x)>(f[i]-f[q[tail]])*(a[q[tail-1]+1].x-a[q[tail]+1].x)) tail--;
    		q[++tail]=i;
    	}
    	printf("%lld\n",f[cnt]);
    	return 0;
    }
    

    by lych

    2016.1.9

    展开全文
  • 就像做递归的题,看的懂答案,但下不了手,关于递归的,我之前也写过一篇套路的文章,如果对递归不大懂的,强烈建议看一看:为什么你学不会递归,告别递归,谈谈我的经验 对于动态规划,春招秋招时好多题都会用到...

    动态规划难吗?说实话,我觉得很难,特别是对于初学者来说,我当时入门动态规划的时候,是看 0-1 背包问题,当时真的是一脸懵逼。后来,我遇到动态规划的题,看的懂答案,但就是自己不会做,不知道怎么下手。就像做递归的题,看的懂答案,但下不了手,关于递归的,我之前也写过一篇套路的文章,如果对递归不大懂的,强烈建议看一看:为什么你学不会递归,告别递归,谈谈我的经验

    对于动态规划,春招秋招时好多题都会用到动态规划,一气之下,再 leetcode 连续刷了几十道题

    在这里插入图片描述

    之后,豁然开朗 ,感觉动态规划也不是很难,今天,我就来跟大家讲一讲,我是怎么做动态规划的题的,以及从中学到的一些套路。相信你看完一定有所收获

    如果你对动态规划感兴趣,或者你看的懂动态规划,但却不知道怎么下手,那么我建议你好好看以下,这篇文章的写法,和之前那篇讲递归的写法,是差不多一样的,将会举大量的例子。如果一次性看不完,建议收藏,同时别忘了素质三连

    为了兼顾初学者,我会从最简单的题讲起,后面会越来越难,最后面还会讲解,该如何优化。因为 80% 的动规都是可以进行优化的。不过我得说,如果你连动态规划是什么都没听过,可能这篇文章你也会压力山大。

    一、动态规划的三大步骤

    动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤,

    如果你听不懂,也没关系,下面会有很多例题讲解,估计你就懂了。之所以不配合例题来讲这些步骤,也是为了怕你们脑袋乱了

    第一步骤:定义数组元素的含义,上面说了,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?

    第二步骤:找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]…dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。

    学过动态规划的可能都经常听到最优子结构,把大的问题拆分成小的问题,说时候,最开始的时候,我是对最优子结构一梦懵逼的。估计你们也听多了,所以这一次,我将换一种形式来讲,不再是各种子问题,各种最优子结构。所以大佬可别喷我再乱讲,因为我说了,这是我自己平时做题的套路。

    第三步骤:找出初始值。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值

    由了初始值,并且有了数组元素之间的关系式,那么我们就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样,这道题也就解出来了。

    不懂?没事,我们来看三四道例题,我讲严格按这个步骤来给大家讲解。

    二、案例详解

    案例一、简单的一维 DP

    问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

    (1)、定义数组元素的含义

    按我上面的步骤说的,首先我们来定义 dp[i] 的含义,我们的问题是要求青蛙跳上 n 级的台阶总共由多少种跳法,那我们就定义 dp[i] 的含义为:跳上一个 i 级的台阶总共有 dp[i] 种跳法。这样,如果我们能够算出 dp[n],不就是我们要求的答案吗?所以第一步定义完成。

    (2)、找出数组元素间的关系式

    我们的目的是要求 dp[n],动态规划的题,如你们经常听说的那样,就是把一个规模比较大的问题分成几个规模比较小的问题,然后由小的问题推导出大的问题。也就是说,dp[n] 的规模为 n,比它规模小的是 n-1, n-2, n-3… 也就是说,dp[n] 一定会和 dp[n-1], dp[n-2]…存在某种关系的。我们要找出他们的关系。

    那么问题来了,怎么找?

    这个怎么找,是最核心最难的一个,我们必须回到问题本身来了,来寻找他们的关系式,dp[n] 究竟会等于什么呢?

    对于这道题,由于情况可以选择跳一级,也可以选择跳两级,所以青蛙到达第 n 级的台阶有两种方式

    一种是从第 n-1 级跳上来

    一种是从第 n-2 级跳上来

    由于我们是要算所有可能的跳法的,所以有 dp[n] = dp[n-1] + dp[n-2]。

    (3)、找出初始条件

    当 n = 1 时,dp[1] = dp[0] + dp[-1],而我们是数组是不允许下标为负数的,所以对于 dp[1],我们必须要直接给出它的数值,相当于初始值,显然,dp[1] = 1。一样,dp[0] = 0.(因为 0 个台阶,那肯定是 0 种跳法了)。于是得出初始值:

    dp[0] = 0.
    dp[1] = 1.
    即 n <= 1 时,dp[n] = n.

    三个步骤都做出来了,那么我们就来写代码吧,代码会详细注释滴。

    int f( int n ){
    	if(n <= 1)
    	return n;
    	// 先创建一个数组来保存历史数据
    	int[] dp = new int[n+1];
    	// 给出初始值
    	dp[0] = 0;
    	dp[1] = 1;
    	// 通过关系式来计算出 dp[n]
    	for(int i = 2; i <= n; i++){
    		dp[i] = dp[i-1] + dp[-2];
    	}
    	// 把最终结果返回
    	return dp[n];
    }
    
    (4)、再说初始化

    大家先想以下,你觉得,上面的代码有没有问题?

    答是有问题的,还是错的,错在对初始值的寻找不够严谨,这也是我故意这样弄的,意在告诉你们,关于初始值的严谨性。例如对于上面的题,当 n = 2 时,dp[2] = dp[1] + dp[0] = 1。这显然是错误的,你可以模拟一下,应该是 dp[2] = 2。

    也就是说,在寻找初始值的时候,一定要注意不要找漏了,dp[2] 也算是一个初始值,不能通过公式计算得出。有人可能会说,我想不到怎么办?这个很好办,多做几道题就可以了。

    下面我再列举三道不同的例题,并且,再在未来的文章中,我也会持续按照这个步骤,给大家找几道有难度且类型不同的题。下面这几道例题,不会讲的特性详细哈。实际上 ,上面的一维数组是可以把空间优化成更小的,不过我们现在先不讲优化的事,下面的题也是,不讲优化版本。

    案例二:二维数组的 DP

    我做了几十道 DP 的算法题,可以说,80% 的题,都是要用二维数组的,所以下面的题主要以二维数组为主,当然有人可能会说,要用一维还是二维,我怎么知道?这个问题不大,接着往下看。

    问题描述

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

    问总共有多少条不同的路径?

    在这里插入图片描述

    这是 leetcode 的 62 号题:https://leetcode-cn.com/problems/unique-paths/

    还是老样子,三个步骤来解决。

    步骤一、定义数组元素的含义

    由于我们的目的是从左上角到右下角一共有多少种路径,那我们就定义 dp[i] [j]的含义为:当机器人从左上角走到(i, j) 这个位置时,一共有 dp[i] [j] 种路径。那么,dp[m-1] [n-1] 就是我们要的答案了。

    注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 右下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要找的答案。

    步骤二:找出关系数组元素间的关系式

    想象以下,机器人要怎么样才能到达 (i, j) 这个位置?由于机器人可以向下走或者向右走,所以有两种方式到达

    一种是从 (i-1, j) 这个位置走一步到达

    一种是从(i, j - 1) 这个位置走一步到达

    因为是计算所有可能的步骤,所以是把所有可能走的路径都加起来,所以关系式是 dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]。

    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n-1] 和所有的 dp[0….m-1] [0]。这个还是非常容易计算的,相当于计算机图中的最上面一行和左边一列。因此初始值如下:

    dp[0] [0….n-1] = 1; // 相当于最上面一行,机器人只能一直往左走

    dp[0…m-1] [0] = 1; // 相当于最左面一列,机器人只能一直往下走

    撸代码

    三个步骤都写出来了,直接看代码

    public static int uniquePaths(int m, int n) {
        if (m <= 0 || n <= 0) {
            return 0;
        }
    
        int[][] dp = new int[m][n]; // 
      	// 初始化
      	for(int i = 0; i < m; i++){
          dp[i][0] = 1;
        }
      	for(int i = 0; i < n; i++){
          dp[0][i] = 1;
        }
    		// 推导出 dp[m-1][n-1]
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
    

    O(n*m) 的空间复杂度可以优化成 O(min(n, m)) 的空间复杂度的,不过这里先不讲

    案例三、二维数组 DP

    写到这里,有点累了,,但还是得写下去,所以看的小伙伴,你们可得继续看呀。下面这道题也不难,比上面的难一丢丢,不过也是非常类似

    问题描述

    给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

    **说明:**每次只能向下或者向右移动一步。

    举例:
    输入:
    arr = [
      [1,3,1],
      [1,5,1],
      [4,2,1]
    ]
    输出: 7
    解释: 因为路径 1→3→1→1→1 的总和最小。
    

    和上面的差不多,不过是算最优路径和,这是 leetcode 的第64题:https://leetcode-cn.com/problems/minimum-path-sum/

    还是老样子,可能有些人都看烦了,哈哈,但我还是要按照步骤来写,让那些不大懂的加深理解。有人可能觉得,这些题太简单了吧,别慌,小白先入门,这些属于 medium 级别的,后面在给几道 hard 级别的。

    步骤一、定义数组元素的含义

    由于我们的目的是从左上角到右下角,最小路径和是多少,那我们就定义 dp[i] [j]的含义为:当机器人从左上角走到(i, j) 这个位置时,最下的路径和是 dp[i] [j]。那么,dp[m-1] [n-1] 就是我们要的答案了。

    注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 由下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要走的答案。

    步骤二:找出关系数组元素间的关系式

    想象以下,机器人要怎么样才能到达 (i, j) 这个位置?由于机器人可以向下走或者向右走,所以有两种方式到达

    一种是从 (i-1, j) 这个位置走一步到达

    一种是从(i, j - 1) 这个位置走一步到达

    不过这次不是计算所有可能路径,而是计算哪一个路径和是最小的,那么我们要从这两种方式中,选择一种,使得dp[i] [j] 的值是最小的,显然有

    dp[i] [j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j];// arr[i][j] 表示网格种的值
    
    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n-1] 和所有的 dp[0….m-1] [0]。这个还是非常容易计算的,相当于计算机图中的最上面一行和左边一列。因此初始值如下:

    dp[0] [j] = arr[0] [j] + dp[0] [j-1]; // 相当于最上面一行,机器人只能一直往左走

    dp[i] [0] = arr[i] [0] + dp[i] [0]; // 相当于最左面一列,机器人只能一直往下走

    代码如下
    public static int uniquePaths(int[][] arr) {
      	int m = arr.length;
      	int n = arr[0].length;
        if (m <= 0 || n <= 0) {
            return 0;
        }
    
        int[][] dp = new int[m][n]; // 
      	// 初始化
      	dp[0][0] = arr[0][0];
      	// 初始化最左边的列
      	for(int i = 1; i < m; i++){
          dp[i][0] = dp[i-1][0] + arr[i][0];
        }
      	// 初始化最上边的行
      	for(int i = 1; i < n; i++){
          dp[0][i] = dp[0][i-1] + arr[0][i];
        }
    		// 推导出 dp[m-1][n-1]
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + arr[i][j];
            }
        }
        return dp[m-1][n-1];
    }
    

    O(n*m) 的空间复杂度可以优化成 O(min(n, m)) 的空间复杂度的,不过这里先不讲

    案例 4:编辑距离

    这次给的这道题比上面的难一些,在 leetcdoe 的定位是 hard 级别。好像是 leetcode 的第 72 号题。

    问题描述

    给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

    你可以对一个单词进行如下三种操作:

    插入一个字符
    删除一个字符
    替换一个字符

    输入: word1 = "horse", word2 = "ros"
    输出: 3
    解释: 
    horse -> rorse (将 'h' 替换为 'r')
    rorse -> rose (删除 'r')
    rose -> ros (删除 'e')
    

    解答

    还是老样子,按照上面三个步骤来,并且我这里可以告诉你,90% 的字符串问题都可以用动态规划解决,并且90%是采用二维数组。

    步骤一、定义数组元素的含义

    由于我们的目的求将 word1 转换成 word2 所使用的最少操作数 。那我们就定义 dp[i] [j]的含义为:当字符串 word1 的长度为 i,字符串 word2 的长度为 j 时,将 word1 转化为 word2 所使用的最少操作次数为 dp[i] [j]

    有时候,数组的含义并不容易找,所以还是那句话,我给你们一个套路,剩下的还得看你们去领悟。

    步骤二:找出关系数组元素间的关系式

    接下来我们就要找 dp[i] [j] 元素之间的关系了,比起其他题,这道题相对比较难找一点,但是,不管多难找,大部分情况下,dp[i] [j] 和 dp[i-1] [j]、dp[i] [j-1]、dp[i-1] [j-1] 肯定存在某种关系。因为我们的目标就是,从规模小的,通过一些操作,推导出规模大的。对于这道题,我们可以对 word1 进行三种操作

    插入一个字符
    删除一个字符
    替换一个字符

    由于我们是要让操作的次数最小,所以我们要寻找最佳操作。那么有如下关系式:

    一、如果我们 word1[i] 与 word2 [j] 相等,这个时候不需要进行任何操作,显然有 dp[i] [j] = dp[i-1] [j-1]。(别忘了 dp[i] [j] 的含义哈)。

    二、如果我们 word1[i] 与 word2 [j] 不相等,这个时候我们就必须进行调整,而调整的操作有 3 种,我们要选择一种。三种操作对应的关系试如下(注意字符串与字符的区别):

    (1)、如果把字符 word1[i] 替换成与 word2[j] 相等,则有 dp[i] [j] = dp[i-1] [j-1] + 1;

    (2)、如果在字符串 word1末尾插入一个与 word2[j] 相等的字符,则有 dp[i] [j] = dp[i] [j-1] + 1;

    (3)、如果把字符 word1[i] 删除,则有 dp[i] [j] = dp[i-1] [j] + 1;

    那么我们应该选择一种操作,使得 dp[i] [j] 的值最小,显然有

    dp[i] [j] = min(dp[i-1] [j-1],dp[i] [j-1],dp[[i-1] [j]]) + 1;

    于是,我们的关系式就推出来了,

    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n] 和所有的 dp[0….m] [0]。这个还是非常容易计算的,因为当有一个字符串的长度为 0 时,转化为另外一个字符串,那就只能一直进行插入或者删除操作了。

    代码如下
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1 + 1][n2 + 1];
        // dp[0][0...n2]的初始值
        for (int j = 1; j <= n2; j++) 
        	dp[0][j] = dp[0][j - 1] + 1;
        // dp[0...n1][0] 的初始值
        for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;
    		// 通过公式推出 dp[n1][n2]
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
              	// 如果 word1[i] 与 word2[j] 相等。第 i 个字符对应下标是 i-1
                if (word1.charAt(i - 1) == word2.charAt(j - 1)){
                	p[i][j] = dp[i - 1][j - 1];
                }else {
                   dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
                }         
            }
        }
        return dp[n1][n2];  
    }
    

    最后说下,如果你要练习,可以去 leetcode,选择动态规划专题,然后连续刷几十道,保证你以后再也不怕动态规划了。当然,遇到很难的,咱还是得挂。

    Leetcode 动态规划直达:https://leetcode-cn.com/tag/dynamic-programming/

    三、总结

    上面的这些题,基本都是不怎么难的入门题,除了最后一道相对难一点,本来是要在写几道难一点,并且讲如何优化的,不过看了下字数,文章有点长了,关于如何优化的,后面再讲吧,在之后的文章中,我也会按照这个步骤,在给大家讲四五道动态规划 hard 级别的题,会放在每天推文的第二条给大家学习。如果大家感兴趣,文章看的人多,那么优化篇很快就会撸出来,大家想要第一时间观看我的文章的,可以关注我的个人微信公众号『苦逼的码农』,不过感兴趣的人很少的话,动力比较少,可能就会慢一些,所以各位小伙伴,如果觉得有收获,不放三连走起来,嘻嘻。

    有收获?希望老铁们来个三连击,给更多的人看到这篇文章

    1、给俺点个赞呗,可以让更多的人看到这篇文章,顺便激励下我,嘻嘻。

    2、老铁们,关注我的原创微信公众号「帅地玩编程」,专注于写算法 + 计算机基础知识(计算机网络+ 操作系统+数据库+Linux)。

    保存让你看完有所收获,不信你打我。后台回复『电子书』送你一份精选电子书大礼包,包含各类技能的优质电子书。

    展开全文
  • CRT是什么意思?LCD又是什么意思

    千次阅读 2008-11-20 17:41:00
    CRT是什么意思?LCD又是什么意思 CRT(Cathode Ray Tube)是阴极射线管。是应用较为广泛的一种显示技术。CRT投影机把输入的信号源分解到R(红)、G(绿)B(蓝)三个CRT管的荧光屏上,在高压作用下发光信号放大、...
  • Android性能优化系列之电量优化

    万次阅读 2017-07-07 01:03:24
    电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情,随着Android开的性能要求越来越高,电量的优化,也显得格外重要,一个耗电的应用,用户肯定会毫不犹豫的进行卸载,所以本篇...
  • 什么是SDK? SDK是什么意思

    千次阅读 2018-08-10 09:44:23
    其实很简单,SDK 就是 Software Development Kit 的缩写,中文意思就是“软件开发工具包”。这是一个覆盖面相当广泛的名词,可以这么说:辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做“SDK”。具体到...
  • 游戏中的优化指的是什么

    千次阅读 2015-09-06 10:53:35
    游戏软件的优化和一般软件是有一些区别。 游戏通常是软实时(soft real-time),就是说运行上有时间限制,但没有硬实时般严格。 先谈固定硬件的游戏平台,如游戏机和街机。在这些平台上,通常会设置固定的帧率...
  • LQR是什么意思

    千次阅读 2016-03-04 17:39:49
    对于线性系统的控制器设计问题,如果其性能指标是状态变量和(或)控制变量的二次型函数的积分,则这种动态系统的最优化问题称为线性系统二次型性能指标的最优控制问题,简称为线性二次型最优控制问题或线性二次问题。...
  • SEO站内优化需要做什么工作

    千次阅读 2018-03-01 10:49:22
    SEO优化一直是很多站长非常重视的一件事情,今年的优化重点主要是在网站内容这一块,也就是站内优化,现在像外链建设,作弊呀在现在的搜索板块几乎消失殆尽了,因此很多站长也把注意力放在站内SEO优化的上面。...
  • 生成动态规划表dp 之后,得到最长公共子串是非常容易的。如 str1 = "cd1234" str2="abd1234"的最长公共子串长度是 dp[5][6]=4;证明str1[5]相左4个字符就是最长公共子串的内容了。 下面是实现代码: #...
  • 百度快照更新是什么意思

    千次阅读 2016-10-18 17:44:30
    关于这个观点我们先看看什么是百度快照?百度快照的作用是什么?我们有该如何让百度快照持续更新呢?    一、百度快照是什么?  快照即为Web Cache,可以翻译为网页缓存,当搜索引擎派出蜘蛛去...
  • Android性能优化(4):UI渲染机制以及优化

    千次阅读 多人点赞 2019-11-20 09:38:34
    渲染优化方式2.1 过度绘制优化2.1.1 Show GPU overdraw2.1.2 Profile GPU Rendering2.2 卡顿优化2.2.1 SysTrace2.2.2 TraceView 在从Android 6.0源码的角度剖析View的绘制原理一文中,我们了解到View的绘制流程有...
  • Makefile 中gcc -lm -ldl是什么意思

    万次阅读 2018-11-01 16:56:00
    gcc -lm -ldl是什么意思?----包含动态链接库 到书上出现gcc -lm 的选项,甚是不解,在网上查阅了一些资料,访问了一些论坛整理下。 初学linux和gcc,C代码调用math.h中的函数有问题,如sqrt函数。若如sqrt(3.0)则...
  • 程序优化

    万次阅读 2016-01-17 18:29:48
    按照优化的侧重点不同,程序优化可分为运行速度优化和代码尺寸优化。 运行速度优化是指在充分掌握软硬件特性的基础上,通过应用程序结构调整等手段来降低完成指定任务所需执行的指令数。在同一个处理器上,经过...
  • 什么是动态定价?

    千次阅读 2020-11-26 17:09:23
    比如购买机票,预订酒店客房或叫出租车服务,所要付的费用其实并不是一成不变的,而是根据市场需求在调整,这就是动态定价。 以往,动态定价是基于过去的销售数据。通过分析季节性和周期性趋势来预测市场需求。现...
  • 参数列表里的冒号是什么意思?Pyhon冒号在括号里?Python括号里加:冒号是什么意思?参数列表里加:冒号?def (context : Context)的冒号是什么意思? 今天群里有位群友问了这样一个问题: 正想回答这位群友,却...
  • 网站优化 14条--雅虎十四条优化原则

    千次阅读 2016-02-23 11:23:51
    雅虎十四条优化原则
  • 最近为了研究索引的知识,特地去MySQL 官网研读了一番,发现MySQL官网有比较全面的MySQL优化方案和知识背景,所以希望通过一系列文章,将官网的知识翻译总结一下,避免日后去网上胡乱搜索产生不必要的知识勘误风险。...
  • 记得,如果有人问你做数据库优化最有效的方式是什么?`SQL优化、分布式集群、分库分表!`干就完了~ 但你对分库分表理解多少呢?什么时候该分表?有几种分法儿?别想了,快上车,5分钟学会!
  • 1查找元素的优化2改变DOM,包括添加,修改,删除DOM3改变DOM的样式类等1查找元素的优化 因为ID是唯一的,也有原始的方法,因此使用ID查找元素是最快的,其次的是根据类和类型查找元素,通过属性查找元素是最慢的,...
  • Android性能优化—布局优化技巧

    千次阅读 2017-03-21 19:12:08
    安卓 布局优化 提高性能
  • squid 优化指南

    万次阅读 2011-07-01 10:38:00
    很多squid 优化只限于在 squid参数和系统参数上面的调整。但是这个实在只是细枝末节的事情,只要不是太弱智的配置导致无法缓存,squid的性能不会有太大差距,也就提高10%左右,只有实际的业务针对squid 进行一些调整...
  • 机器学习为什么要学习最优化呢?

    千次阅读 2015-06-02 16:34:42
    优化方法(也称做运筹学方法)是近几十年形成的,它主要运用数学方法研究各种系统的优化途径及方案,为决策者提供科学决策的依据。最优化方法的主要研究对象是各种有组织系统的管理问题及其生产经营活动。最优化...
  • -- 优化专题: 整理一系列的专题:比如APK瘦身、插件化、程序架构、性能优化、自定义view、增量升级、移动开发各种技术解决方案等。 Android后期发展的五大趋势:一、性能优化;二、高级UI;三、JNI/NDK开发;...
  • GPU优化方法

    千次阅读 2010-01-23 23:09:00
    CUDA优化的最终目的是:在最短的时间内,在允许的误差范围内完成给定的计算任务。在这里,“最短的时间”是指整个程序运行的时间,更侧重于计算的吞吐量,而不是单个数据的延迟。在开始考虑使用GPU和CPU协同计算之前...
  • 实现跨组织、跨区域、跨部门信息整合的现代化管理系统,包含营销、客户、项目、销售、合同、采购、库存、生产、产品、售后、财务、人资、办公等很多功能,通过对企业资源的集成和优化资源配置,帮助企...
  • slam是什么意思?一文带你读懂SLAM

    万次阅读 2018-09-17 14:11:10
    同时,视觉信息可以较为容易的被用来跟踪和预测场景中的动态目标,如行人、车辆等,对于在复杂动态场景中的应用这是至关重要的。 通过对比我们发现,激光SLAM和视觉SLAM各擅胜场,单独使用都有其局限性,而融合...
  • Oracle优化05-执行计划

    千次阅读 2016-12-26 23:01:52
    思维导图系列文章Oracle-SQL Explain Plan解读概述如果要分析某条SQL的性能问题,通常来讲,我们首先要看SQL的执行计划,看看SQL... 看懂执行计划变成了SQL优化(其实在大多数的情况下,SQL优化指的是SQL的性能问题定位

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 105,159
精华内容 42,063
关键字:

动态优化是什么意思