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

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

算法三:
我们再进一步考虑 cmax cmin 是否都需要枚举?其实不需要,我们可以只枚举 cmin ,因为对于每个 cmin ,我们希望 cmax 尽可能小,所以其实对于每个 i ci肯定是取 ai+bi aibi 中,在大于等于 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也好,枚举 cmin cmax ,其实都是从局部出发的,我们可以试试从宏观的角度来思考这个问题. 宏观来看,就是数轴上有2N个点,每个点会属于某个i,我们希望在数轴上选最短的一段,使得这一段内的点分属于1到N,或者说使得对于每个i, ai+bi aibi 至少有一个在这一段中. 于是我们可以用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;
} 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值