今天第一次注册使用blog

转载 2017年02月23日 12:38:22

以前刷航电OJ时看到好多大神都用博客,本来自己想注册一个的,因为懒一直没弄大笑刚好以后作业都要用博客去完成,从今天起就开始使用了

大家共勉吧。

今天第一天写在这代码,想了想还是把以前做的一题动归放着(当时想了好久,后来看别人的文章才明白的)

ps:以下内容非原创

最大子段和问题(51NOD 1049)

 
给出一个整数数组a(正负数都有),如何找出一个连续子数组(可以一个都不取,那么结果为0),使得其中的和最大?

例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。



看见这个问题你的第一反应是用什么算法? 

(1) 枚举?对,枚举是万能的!枚举什么?子数组的位置!好枚举一个开头位置i,一个结尾位置j>=i,再求a[i..j]之间所有数的和,找出最大的就可以啦。好的,时间复杂度?

(1.1)枚举i,O(n)
(1.2)枚举j,O(n)
(1.3)求和a[i..j],O(n)

大概是这样一个计算方法:

for(int i = 1; i <= n; i++)
{
    for(int j = i; j <= n; j++)
    {
        int sum = 0;
        for(int k = i; k <= j; k++)
            sum += a[k];
           
        max = Max(max, sum);
    }
}

所以是O(n^3), 复杂度太高?降低一下试试看?
(2) 仍然是枚举! 能不能在枚举的同时计算和?
(2.1)枚举i,O(n)
(2. 2)枚举j,O(n) ,这里我们发现a[i..j]的和不是a[i..j – 1]的和加上a[j]么?所以我们在这里当j增加1的时候把a[j]加到之前的结果上不就可以了么?对!所以我们毫不费力地降低了复杂度,得到了一个新地时间复杂度为O(n^2)的更快的算法。

大概是这样一段代码:

for(int i = 1; i <= n; i++)
{
    int sum = 0;
   
    for(int j = i; j <= n; j++)
    {
        sum += a[j];
        max = Max(max, sum);
    }
}

是不是到极限了?远远不止!

(3)分治一下?

我们从中间切开数组,原数组的最大子段和要么是两个子数组的最大子段和(分), 要么是跨越中心分界点的最大子段和(合)。 那么跨越中心分界点的最大子段合怎么计算呢?仍然是枚举! 从中心点往左边找到走到哪里可以得到最大的合,再从中心点往右边检查走到哪里可以得到最大的子段合,加起来就可以了。可见原来问题之所以难,是因为我们不知道子数组从哪里开始,哪里结束,没有“着力点”,有了中心位置这个“着力点”,我们可以很轻松地通过循环线性时间找到最大子段和。

于是算法变成了

(3.1)拆分子数组分别求长度近乎一半的数组的最大子段和sum1, sum2

时间复杂度 2* T(n / 2)

(3.2)从中心点往两边分别分别找到最大的和,找到跨越中心分界点的最大子段和sum3 时间复杂度 O(n)

那么总体时间复杂度是T(n) = 2 * T(n / 2) + O(n) = O(nlogn), 又优化了一大步,不是吗?

还能优化吗?再想想,别放弃!

我们在解法(3)里需要一个“着力点”达到O(n)的子问题时间复杂度,又在解法(2)里轻易地用之前的和加上一个新的元素得到现在的和,那么“之前的和”有那么重要么?如果之前的和是负数呢?显然没用了吧?我们要一段负数的和,还不如从当前元素重新开始了吧?

再想想,如果我要选择a[j],那么“之前的和”一定是最大的并且是正的。不然要么我把“之前的和”换成更优,要么我直接从a[j]开始,不是更好么?

动态规划大显身手。我们记录dp[i]表示以a[i]结尾的全部子段中最大的和。我们看一下刚才想到的,我取不取a[i – 1],如果取a[i – 1]则一定是取以a[i – 1]结尾的子段和中最大的一个,所以是dp[i – 1]。 那如果不取dp[i – 1]呢?那么我就只取a[i]孤零零一个好了。注意dp[i]的定义要么一定取a[i]。 那么我要么取a[i – 1]要么不取a[i -1]。 那么那种情况对dp[i]有利? 显然取最大的嘛。所以我们有dp[i] = max(dp[i – 1] + a[i], a[i]) 其实它和dp[i] = max(dp[i – 1] , 0) + a[i]是一样的,意思是说之前能取到的最大和是正的我就要,否则我就不要!初值是什么?初值是dp[1] = a[1],因为前面没的选了。

那么结果是什么?我们要取的最大子段和必然以某个a[i]结尾吧?那么结果就是max(dp[i])了。

这样,我们的时间复杂度是O(n),空间复杂度也是O(n)——因为要记录dp这个数组。

#include<cstdio>
#include<algorithm> 
using namespace std; 

long long a[50005],dp[50005];

int main()
{
    int n,i;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=1;i<=n;i++)
        scanf("%lld",&a[i]);
        dp[1]=a[1];
        long long ans;
        ans=-999999999999999999;
        for(i=2;i<=n;i++)
        {
            dp[i]=max(dp[i-1]+a[i],a[i]);
            ans=max(dp[i],ans);
        }
        printf("%lld\n",ans);
    }
    return 0;
 }
 
算法达到最优了吗? 好像是!还可以优化!我们注意到dp[i] = max(dp[i - 1], 0) + a[i], 看它只和dp[i – 1]有关,我们为什么要把它全记录下来呢?为了求所有dp[i]的最大值?不,最大值我们也可以求一个比较一个嘛。

我们定义endmax表示以当前元素结尾的最大子段和,当加入a[i]时,我们有endmax’ = max(endmax, 0) + a[i], 然后再顺便记录一下最大值就好了。

伪代码如下;(数组下标从1开始)

endmax = answer = a[1]
for i = 2 to n do
    endmax = max(endmax, 0) + a[i]
    answer = max(answer, endmax)
endfor

时间复杂度?O(n)!空间复杂度?O(1)! 简单吧?我们不仅优化了时间复杂度和空间复杂度,还使代码变得简单明了,更不容易出错。

老生常谈的问题来了。我们如何找到一个这样的子段?请看上面的为伪代码endmax = max(endmax, 0) + a[i], 对于endmax它对应的子段的结尾显然是a[i],我们怎么知道这个子段的开头呢? 就看它有没有被更新。也就是说如果endmax’ = endmax + a[i]则对应子段的开头就是之前的子段的开头。否则,显然endmax开头和结尾都是a[i]了,让我们来改一下伪代码:

start = 1
answerstart = asnwerend = 1
endmax = answer = a[1]
for end = 2 to n do
    if endmax > 0 then
        endmax += a[end]
    else
        endmax = a[end]
        start = end
    endif
    if endmax > answer then
        answer = endmax
        answerstart = start
        answerend = end
    endif
endfor

这里我们直接用end作为循环变量,通过更新与否决定start是否改变。

总结:通过不断优化,我们得到了一个时间复杂度为 O(n),空间复杂度为O(1)的简单的动态规划算法。动态规划,就这么简单!优化无止境!

转载自51nod,侵删


从今天起,开启blog生活方式

断断续续的编程让我对极客的世界毫无自信可言,经历过N次3分钟热度的我不想在徘徊在懂点皮毛缺半点项目都做不出来的阶段,我要编程,我想编程,以后我也要进入此坑,至少现在的我是这么想的,孤注一掷,加油吧少年...

今天开始学PID电机控制,这个作者写得很不错,和大家分享一下~~~ PID控制算法通俗理解 作者:whut_wj 来源:http://blog.eccn.com/space.php?uid=35

今天开始学PID电机控制,这个作者写得很不错,和大家分享一下~~~ PID控制算法通俗理解 作者:whut_wj 来源:http://blog.eccn.com/space....

今天用 CSS+DIV 为主,仿造了一个 Blog 静态网页,

调整什么的好伤神啊...            博客
  • imwtr
  • imwtr
  • 2014年07月14日 22:46
  • 627

【第三篇blog】第一次模拟赛反思与解析

模拟赛题目选自USACO铜组个人翻译,严禁转载——————————————————————我是更新提示分割线———————————————————————使用完整题目,切勿转载。———————————...

今天第一次面试别人, 大概聊了近30分钟, 呵呵。

由于朋友临时有事, 所以今天我代替一朋友进行一个电话面试, 第一次面试他人(不是应聘我们公司), 我以很认真负责的态度完成这个过程, 大概近30分钟。 主要是技术面试, 在近30分钟内, 我与被面试者...
  • stpeace
  • stpeace
  • 2014年09月05日 23:51
  • 4896

今天第一次开始写博客,作为新入门的程序员记录自己的学习工作中的问题。

刚进公司差不多20天,真的是忽悠来的工作

【第一篇blog】“从A+B Problem到我的第一次NOIP”

第一次写博客勿喷好了,开始吧—————————————我是华丽丽的分割线——————————————回顾 接触信奥这门竞赛也有一段时间了,自从暑假开始学OI以来,第一次在学习上遇到了坎。我们的老...

今天第一次系统返回的数据里面有笑脸,探测笑脸ASCII 01

今天好开心啊,第一次系统返回的数据里面有笑脸,所以顺便探测了一下笑脸☺ASCII 01。 实(搞)验(怪)用的源码,如下...

端口占用问题,今天学习的时候又出现了这个问题,找了度娘,特此记录以下。第一次写博客,希望自己能坚持下来。大家共同进步

 端口占用(这张图是百度搜的,我的端口改了是8888,即(8005,8888,8009)) 在运行界面中输入cmd命令: ...

我是个新人今天第一次来这里写博客。欢迎大家多多指教

http://www.chiznews.com/bk/rw/11151.html http://www.dzrbs.com/2014/0928/20149289103292.html http:/...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:今天第一次注册使用blog
举报原因:
原因补充:

(最多只允许输入30个字)