从一个问题的多个算法看算法的优化

来源:互联网 发布:台达触摸屏编程软件3.0 编辑:程序博客网 时间:2024/06/08 13:30

题目:给定两个数列a1,a2,,anb1,b2,,bn,你可以用这两个数列来产生新数列c1,c2,,cn. 产生的方式是令ci=ai+biaibi. 用cmaxcmin分别表示c1,c2,,cn的最大值和最小值,求cmaxcmin的最小值. 每个aibi的取值都是-100 000 000到100 000 000范围内的整数.
算法一:
暴力穷举数列c的所有可能,看cmaxcmin能取得的最小值是多少. 因为每个ci有两种可能的取值,所以总共有2n个可能的数列,算法复杂度O(N2N).
算法二:
我们换个角度来想,算法一之所以复杂度高,是因为数列c的可能性太多了,那问题中有没有哪些值可能性并没有那么多,但只要我们枚举这些值就可以得到答案呢?有,比如cmaxcmin. 不管是cmax还是cmin,它们都只能取某个ci=ai+biaibi,因而都只有2n种取值可能. 这样我们就可以得到更好的算法,我们枚举cmaxcmin的所有可能取值(步骤1),然后判断对于每个i,ai+biaibi是否至少有一个落在[cmin, cmax]的范围内(步骤2). 步骤1的复杂度是O(N2),步骤2的复杂度是O(N),算法总复杂度O(N3). 程序如下:

#include <cstdio>const int N = 100 + 10;const int inf = 1000000000;int A[N], B[N];int n;inline bool in_range(int left, int right, int x){    return x >= left && x <= right;}bool check(int left, int right, int& d){    int tmp, i;    if (left > right)    {        tmp = left;        left = right;        right = tmp;    }    for (i=0; i<n; i++) if (!in_range(left, right, A[i] + B[i]) && !in_range(left, right, A[i] - B[i])) return false;    d = right - left;    return true;}int main(){    int res, t, d, i, j, k;    scanf("%d", &t);    while (t --)    {        scanf("%d", &n);        for (i=0; i<n; i++) scanf("%d", &A[i]);        for (i=0; i<n; i++) scanf("%d", &B[i]);        if (n == 1)        {            printf("0\n");            continue;        }        res = inf;        for (i=0; i<n-1; i++)            for (j=i+1; j<n; j++)            {                if (check(A[i] + B[i], A[j] + B[j], d) && d < res) res = d;                if (check(A[i] + B[i], A[j] - B[j], d) && d < res) res = d;                if (check(A[i] - B[i], A[j] + B[j], d) && d < res) res = d;                if (check(A[i] - B[i], A[j] - B[j], d) && d < res) res = d;            }        printf("%d\n", res);        }    return 0;}

算法三:
我们再进一步考虑cmaxcmin是否都需要枚举?其实不需要,我们可以只枚举cmin,因为对于每个cmin,我们希望cmax尽可能小,所以其实对于每个ici肯定是取ai+biaibi中,在大于等于cmin的前提下的较小值. 因而对于枚举的每个cmin我们都可以O(N)时间内求出cmax. 算法总复杂度O(N2). 程序比算法二更加简洁.

#include <cstdio>#include <algorithm>using namespace std;const int N = 100 + 10;const int inf = 1000000000;int A[N], B[N];int n;bool check(int left, int& d){    int right, i;    right = left;    for (i=0; i<n; i++)        if (A[i] + B[i] >= left && A[i] - B[i] >= left) right = max(right, min(A[i] + B[i], A[i] - B[i]));        else if (A[i] + B[i] >= left) right = max(right, A[i] + B[i]);        else if (A[i] - B[i] >= left) right = max(right, A[i] - B[i]);        else return false;    d = right - left;    return true;    }int main(){    int res, t, d, i, j;    scanf("%d", &t);    while (t --)    {        scanf("%d", &n);        for (i=0; i<n; i++) scanf("%d", &A[i]);        for (i=0; i<n; i++) scanf("%d", &B[i]);        res = inf;        for (i=0; i<n; i++)        {            if (check(A[i] + B[i], d) && d < res) res = d;            if (check(A[i] - B[i], d) && d < res) res = d;        }        printf("%d\n", res);    }    return 0;} 

算法四:
前面的算法,枚举c也好,枚举cmincmax,其实都是从局部出发的,我们可以试试从宏观的角度来思考这个问题. 宏观来看,就是数轴上有2N个点,每个点会属于某个i,我们希望在数轴上选最短的一段,使得这一段内的点分属于1到N,或者说使得对于每个i,ai+biaibi至少有一个在这一段中. 于是我们可以用O(N)扫描的方法,来解决这个问题. 因为用到排序,所以算法的总复杂度是O(NlogN). 程序如下:

#include <cstdio>#include <algorithm>using namespace std;const int N = 100 + 10;const int inf = 1000000000;struct ITEM{    int val, id;    ITEM(int val=0, int id=0):val(val), id(id){}};int A[N], B[N], cnt[N];ITEM C[2*N];int n;bool operator < (const ITEM& a, const ITEM& b){    return a.val < b.val;}int main(){    int res, t, s, i, j;    scanf("%d", &t);    while (t --)    {        scanf("%d", &n);        for (i=0; i<n; i++) scanf("%d", &A[i]);        for (i=0; i<n; i++) scanf("%d", &B[i]);        for (i=0; i<n; i++)        {            C[i*2] = ITEM(A[i] + B[i], i);            C[i*2+1] = ITEM(A[i] - B[i], i);            cnt[i] = 0;        }        sort(C, C + 2 * n);        s = 0;        for (i=0; i<2*n; i++)        {            cnt[C[i].id] ++;            if (cnt[C[i].id] == 1) s ++;            if (s == n) break;        }        j = 0;        for (; cnt[C[j].id]==2; j++) cnt[C[j].id] --;         res = C[i].val - C[j].val;        for (i++; i<2*n; i++)        {            cnt[C[i].id] ++;            for (; cnt[C[j].id]==2; j++) cnt[C[j].id] --;            res = min(res, C[i].val - C[j].val);        }        printf("%d\n", res);    }    return 0;} 

总结一下,我们设计和优化算法,常见的思路一个是尽可能的减少可能性,可能性越少,需要枚举的就越少;一个是多变换看待问题的角度,有时换个角度就能更容洞悉问题的本质.

2 0
原创粉丝点击