算法竞赛中的时间复杂度选择——以最大连续和问题为例

36 篇文章 3 订阅

最大连续和问题

最大连续和问题。给出一个长度为 n 的序列 A1,A2,,An,求最大连续和。换句话说,要求找到 1ijn ,使得 Ai+Ai+1+...+Aj 尽量大。

时间复杂度为 n3 的算法

LL maxConSumN3(LL *a, LL n) {
    tot = 0;
    conSum = -INF;
    for(LL i = 0; i < n; i++) {
        for(LL j = i; j < n; j++) {
            LL sum = 0;
            for(LL k = i; k <= j; k++) {
                sum += a[k];
                tot++;
            }
            if(sum > conSum) {
                conSum = sum;
            }
        }
    }
    return conSum;
}

时间复杂度为 n2 的算法

LL maxConSumN2(LL *a, LL n) {
    tot = 0;
    conSum = -INF;
    for(LL i = 0; i < n; i++) {
        LL sum = 0;
        for(LL j = i; j < n; j++) {
            sum += a[j];
            tot++;
            if(sum > conSum) {
                conSum = sum;
            }
        }
    }
    return conSum;
}

时间复杂度为 nlog2n 的算法

// 采用分治法
// 对半划分
// 递归求解左半边和右半边的最大连续和
// 递归边界为left=right
// 求解左右连接部分的最大连续和
// 合并子问题:取三者最大值
LL division(LL *a, LL lef, LL righ) {
    // 递归边界
    if(lef == righ) {
        return a[lef];
    }
    LL center = lef + (righ - lef) / 2;
    // 左半边最大连续和

    LL maxLeftSum = division(a, lef, center);
    // 右半边最大连续和
    LL maxRightSum = division(a, center + 1, righ);
    // 左连接部分最大和
    LL maxLeftConSum = -INF;
    LL leftConSum = 0;
    for(LL i = center; i >= lef; i--) {
        leftConSum += a[i];
        tot++;
        if(leftConSum > maxLeftConSum) {
            maxLeftConSum = leftConSum;
        }
    }
    // 右连接部分最大和
    LL maxRightConSum = -INF;
    LL rightConSum = 0;
    for(LL i = center + 1; i <= righ; i++) {
        rightConSum += a[i];
        tot++;
        if(rightConSum > maxRightConSum) {
            maxRightConSum = rightConSum;
        }
    }
    return max(max(maxLeftSum, maxRightSum), maxLeftConSum + maxRightConSum);
}

LL maxConSumNLogN(LL *a, LL n) {
    return division(a, 0, n - 1);
}

时间复杂度为 n 的算法

LL maxConSumN(LL *a, LL n) {
    conSum = -INF;
    LL sum = 0;
    tot = 0;
    for(int i = 0; i < n; i++) {
        sum += a[i];
        tot++;
        if(sum < 0) {
            sum = 0;
        }
        if(sum > conSum) {
            conSum = sum;
        }
    }
    return conSum;
}

测试主程序

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const LL INF = 100000000;

// 总执行次数
LL tot;
// 最大连续和
LL conSum;

// 求最大连续和问题
// n^3的算法
LL maxConSumN3(LL *a, LL n) {
    tot = 0;
    conSum = -INF;
    for(LL i = 0; i < n; i++) {
        for(LL j = i; j < n; j++) {
            LL sum = 0;
            for(LL k = i; k <= j; k++) {
                sum += a[k];
                tot++;
            }
            if(sum > conSum) {
                conSum = sum;
            }
        }
    }
    return conSum;
}

// n^2的算法
LL maxConSumN2(LL *a, LL n) {
    tot = 0;
    conSum = -INF;
    for(LL i = 0; i < n; i++) {
        LL sum = 0;
        for(LL j = i; j < n; j++) {
            sum += a[j];
            tot++;
            if(sum > conSum) {
                conSum = sum;
            }
        }
    }
    return conSum;
}

// nlogn的算法
// 采用分治法
// 对半划分
// 递归求解左半边和右半边的最大连续和
// 递归边界为left=right
// 求解左右连接部分的最大连续和
// 合并子问题:取三者最大值
LL division(LL *a, LL lef, LL righ) {
    // 递归边界
    if(lef == righ) {
        return a[lef];
    }
    LL center = lef + (righ - lef) / 2;
    // 左半边最大连续和

    LL maxLeftSum = division(a, lef, center);
    // 右半边最大连续和
    LL maxRightSum = division(a, center + 1, righ);
    // 左连接部分最大和
    LL maxLeftConSum = -INF;
    LL leftConSum = 0;
    for(LL i = center; i >= lef; i--) {
        leftConSum += a[i];
        tot++;
        if(leftConSum > maxLeftConSum) {
            maxLeftConSum = leftConSum;
        }
    }
    // 右连接部分最大和
    LL maxRightConSum = -INF;
    LL rightConSum = 0;
    for(LL i = center + 1; i <= righ; i++) {
        rightConSum += a[i];
        tot++;
        if(rightConSum > maxRightConSum) {
            maxRightConSum = rightConSum;
        }
    }
    return max(max(maxLeftSum, maxRightSum), maxLeftConSum + maxRightConSum);
}

LL maxConSumNLogN(LL *a, LL n) {
    return division(a, 0, n - 1);
}

// n的算法
LL maxConSumN(LL *a, LL n) {
    conSum = -INF;
    LL sum = 0;
    tot = 0;
    for(int i = 0; i < n; i++) {
        sum += a[i];
        tot++;
        if(sum < 0) {
            sum = 0;
        }
        if(sum > conSum) {
            conSum = sum;
        }
    }
    return conSum;
}

int main() {
    LL a[] = {-2, 3, 4, 5, -6, 7, -1, 2, 6};
    cout << "时间复杂度为N^3的算法:" << maxConSumN3(a, 9);
    cout << "\t 计算次数为:" << tot << endl;

    cout << "时间复杂度为N^2的算法:" << maxConSumN2(a, 9);
    cout << "\t 计算次数为:" << tot << endl;

    tot = 0;
    cout << "时间复杂度为NLogN的算法:" << maxConSumNLogN(a, 9);
    cout << "\t 计算次数为:" << tot << endl;

    cout << "时间复杂度为N的算法:" << maxConSumN(a, 9);
    cout << "\t\t 计算次数为:" << tot << endl;

    return 0;
}

输出结果为

时间复杂度为N^3的算法:20         计算次数为:165
时间复杂度为N^2的算法:20         计算次数为:45
时间复杂度为NLogN的算法:20       计算次数为:29
时间复杂度为N的算法:20           计算次数为:9

Process returned 0 (0x0)   execution time : 0.196 s
Press any key to continue.

算法竞赛中的时间复杂度选择

假设机器速度是每秒108次基本运算,运算量为 n3n2nlog2nn2n (如子集枚举)和 n! (如排列枚举)的算法,在1秒之内能解决最大问题规模 n ,如表所示:


运算量 n! 2n n3 n2 nlog2n n
最大规模 11 26 464 10000 4.5106 100000000 速度扩大两倍以后 11 27 584 14142 8.6106 200000000

运算量随着规模的变化


表还给出了机器速度扩大两倍后,算法所能解决规模的对比。可以看出, n! 2n 不仅能解决的问题规模非常小,而且增长缓慢;最快的 nlog2n n 算法不仅解决问题的规模大,而且增长快。渐进时间复杂为多项式的算法称为多项式时间算法(polymonial-time algorithm),也称有效算法;而n!或者 2n 这样的低效的算法称为指数时间算法(exponentialtime algorithm)。

不过需要注意的是,上界分析的结果在趋势上能反映算法的效率,但有两个不精确性: 一是公式本身的不精确性。例如,“非主流”基本操作的影响、隐藏在大O记号后的低次项和最高项系数;二是对程序实现细节与计算机硬件的依赖性,例如,对复杂表达式的优化计算、把内存访问方式设计得更加“cache友好”等。在不少情况下,算法实际能解决的问题规模与表所示有着较大差异。

尽管如此,表还是有一定借鉴意义的。考虑到目前主流机器的执行速度,多数算法竞赛题目所选取的数据规模基本符合此表。例如,一个指明 n8 的题目,可能 n! 的算法已经足够, n20 的题目需要用到 2n 的算法,而 n300 的题目可能必须用至少 n3 的多项式时间算法了。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
世界顶级程序设计高手的经验总结 【ACM-ICPC全球总冠军】巫泽俊主译 日本ACM-ICPC参赛者人手一册 本书对程序设计竞赛的基础算法和经典问题进行了汇总,分为准备篇、初级篇、级篇与高级篇4章。作者结合自己丰富的参赛经验,对严格筛选的110 多道各类试题进行了由浅入深、由易及难的细致讲解,并介绍了许多实用技巧。每章后附有习题,供读者练习,巩固所学。 本书适合程序设计人员、程序设计竞赛爱好者以及高校计算机专业师生阅读。 目录 · · · · · · 译者序 前言 第1章 蓄势待发——准备篇 1.1  何谓程序设计竞赛 1.2  最负盛名的程序设计竞赛 1.2.1  世界规模的大赛——Google Code Jam(GCJ) 1.2.2  向高排名看齐!——TopCoder 1.2.3  历史最悠久的竞赛—— ACM-ICPC 1.2.4  面向学生的信息学奥林匹克竞赛——JOI-IOI 1.2.5  通过网络自动评测——Online Judge(OJ) 1.3  本书的使用方法 1.3.1  本书所涉及的内容 1.3.2  所用的编程语言 1.3.3  题目描述的处理 1.3.4  程序结构 1.3.5  练习题 1.3.6  读透本书后更上一层楼的练习方法 1.4  如何提交解答 1.4.1  POJ的提交方法 1.4.2  GCJ的提交方法 1.5  以高效的算法为目标 1.5.1  什么是复杂度 1.5.2  关于运行时间 1.6  轻松热身 1.6.1  先从简单题开始 1.6.2  POJ的题目Ants 1.6.3  难度增加的抽签问题 阅读 第2章 初出茅庐——初级篇 2.1  最基础的“穷竭搜索” 2.1.1  递归函数 2.1.2  栈 2.1.3  队列 2.1.4  深度优先搜索 2.1.5  宽度优先搜索 2.1.6  特殊状态的枚举 2.1.7  剪枝 2.2  一往直前!贪心法 2.2.1  硬币问题 2.2.2  区间问题 2.2.3  字典序最小问题 2.2.4  其他例题 2.3  记录结果再利用的“动态规划” 2.3.1  记忆化搜索与动态规划 2.3.2  进一步探讨递推关系 2.3.3  有关计数问题的DP 2.4  加工并存储数据的数据结构 2.4.1  树和二叉树 2.4.2  优先队列和堆 2.4.3  二叉搜索树 2.4.4  并查集 2.5  它们其实都是“图” 2.5.1  图是什么 2.5.2  图的表示 2.5.3  图的搜索 2.5.4  最短路问题 2.5.5  最小生成树 2.5.6  应用问题 2.6  数学问题的解题窍门 2.6.1  辗转相除法 2.6.2  有关素数的基础算法 2.6.3  模运算 2.6.4  快速幂运算 2.7  一起来挑战GCJ的题目(1) 2.7.1  Minimum Scalar Product 2.7.2  Crazy Rows 2.7.3  Bribe the Prisoners 2.7.4  Millionaire 阅读 第3章 出类拔萃——级篇 3.1  不光是查找值!“二分搜索” 3.1.1  从有序数组查找某个值 3.1.2  假定一个解并判断是否可行 3.1.3  最大化最小值 3.1.4  最大化平均值 3.2  常用技巧精选(一) 3.2.1  尺取法 3.2.2  反转(开关问题) 3.2.3  弹性碰撞 3.2.4  折半枚举(双向搜索) 3.2.5  坐标离散化 3.3  活用各种数据结构 3.3.1  线段树 3.3.2  Binary Indexed Tree 3.3.3  分桶法和平方分割 3.4  熟练掌握动态规划 3.4.1  状态压缩DP 3.4.2  矩阵的幂 3.4.3  利用数据结构高效求解 3.5  借助水流解决问题的网络流 3.5.1  最大流 3.5.2  最小割 3.5.3  二分图匹配 3.5.4  一般图匹配 3.5.5  匹配、边覆盖、独立集和顶点覆盖 3.5.6  最小费用流 3.5.7  应用问题 3.6  与平面和空间打交道的计算几何 3.6.1  计算几何基础 3.6.2  极限情况 3.6.3  平面扫描 3.6.4  凸包 3.6.5  数值积分 3.7  一起来挑战GCJ的题目(2) 3.7.1  Numbers 3.7.2  No Cheating 3.7.3  Stock Charts 3.7.4  Watering Plants 3.7.5  Number Sets 3.7.6  Wi-fi Towers 第4章 登峰造极——高级篇 4.1  更加复杂的数学问题 4.1.1  矩阵 4.1.2  模运算的世界 4.1.3  计数 4.1.4  具有对称性的计数 4.2  找出游戏的必胜策略 4.2.1  游戏与必胜策略 4.2.2  Nim 4.2.3  Grundy数 4.3  成为图论大师之路 4.3.1  强连通分量分解 4.3.2  2-SAT 4.3.3  LCA 4.4  常用技巧精选(二) 4.4.1  栈的运用 4.4.2  双端队列的运用 4.4.3  倍增法 4.5  开动脑筋智慧搜索 4.5.1  剪枝 4.5.2  A*与IDA* 4.6  划分、解决、合并:分治法 4.6.1  数列上的分治法 4.6.2  树上的分治法 4.6.3  平面上的分治法 4.7  华丽地处理字符串 4.7.1  字符串上的动态规划算法 4.7.2  字符串匹配 4.7.3  后缀数组 4.8  一起来挑战GCJ的题目(3) 4.8.1  Mine Layer 4.8.2  Year of More Code Jam 4.8.3  Football Team 4.8.4  Endless Knight 4.8.5  The Year of Code Jam 阅读 本书未涉及的拓展主题 书例题列表 参考文献

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值