关闭

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

标签: 枚举优化
224人阅读 评论(0) 收藏 举报
分类:

题目:给定两个数列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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:225次
    • 积分:42
    • 等级:
    • 排名:千里之外
    • 原创:1篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档