codility上的问题 之六 Epsilon 2011

本文探讨了一个在Codility平台上出现的挑战性问题,该问题涉及求解线性分段函数的最小值。通过巧妙地利用排序和堆栈数据结构,作者提出了一种时间复杂度为O(NlogN)、空间复杂度为O(N)的解决方案,有效解决了这个问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

codility上得问的还有这么一个特点是题目本身不难,但是它却能描述的很复杂。比如这个题。

给定 数组A和B都是整数数组,各包括N个数,定义函数F(X,K),U(X)是最大值,D(X)是最小值,S(X)是差。

F(X,K)=A[K]*X + B[K] 
U(X)=max{ F(X,K) : 0 ≤ K < N }
D(X)=min{ F(X,K) : 0 ≤ K < N }
S(X)=U(X) − D(X)

求min S(x)  这里x与结果都可以是任意实数。


其实这就是个线性分段函数的最小值问题。

范围: N [1..10^5]

A,B种的每个数[-10^3..+10^3]

复杂度要求: 时间O(NlogN),空间O(N)

分析: 这个题本身不难,求线性分段函数的极值,我们可以在每个段上分别考虑,因为每个段都是线性的,极值肯定在端点取到,枚举即可。但是它的复杂度很诡异,时间O(NlogN),正好是排序的复杂度。我们先算U(x),就是试图分段出x在不同区间时,它的样子。这个其实就是说判断它每种形式在不同区间的大小。 我们先把函数按斜率排序,斜率相等按截距排。 假设两段是 k1*x + b1和k2 * x + b2   k1 <= k2

如果k1 == k2 那么 b1, b2都直接决定了大小,也就是说 平行线的话,容易考虑。

如果k1 < k2,我们算出它的交点x1,那么再x < x1时k1 * x + b1大一些,x > x1 k2 * x + b2大一些。

同理下一段 k3 *x  + b3  k2 < k3

我们同样求它和k2 * x + b2的交点 x2 ,如果我们发现x2 <= x1,也就是说这个交点比x1来得更早的话,第二段k2 * x + b2是没用的,它不可能是最大值,因为当它要成为最大值之前k3 * x + b3已经比它大了。于是我们把它扔掉就可以了,再用k3 * x + b3和更前一条直线求交点,这个过程就像保存了一个堆栈,每次可能把栈顶弹出来,如果弹出来还要继续判断。最终把当前段和交点压入堆栈。 还是那个道理,每段出入栈最多1次,所以这个复杂度是O(N)的。 (没算排序)。

其实D(X)也可以类似处理,取个相反数就好了。。。。

这个题难点一个在于分段函数极值,不能O(N^2),其实就是要求每条直线超过另外一条直线的位置。

还有一个难点在于最后结果的特殊判断,例如只有一段的话,极值点怎么取? 还有所有直线都平行之类的特殊情况,所以我代码写得很长。


代码:


// you can also use includes, for example:
// #include <algorithm>
#include <algorithm>

int cmp(pair<int,int> &a,pair<int,int> &b) {
    return a.first * b.second - a.second * b.first;
}


pair<int,int> inter(pair<int,int> &a,pair<int,int> &b) {
    return make_pair(b.second - a.second, a.first - b.first);
}

void make(vector<pair<int,int> > &all,vector<pair<int,int> > &p, vector<pair<int,int> > &line) {
int n = all.size(),i,j;
    
    for (i = 0; i < n; ++i) {
        for (j = i; (j < n) && (all[i].first == all[j].first); ++j)
        ;
        i = j - 1;
        if (line.empty()) {
            line.push_back(all[i]);
        }
        else {
            pair<int,int> temp;
            for (j = line.size() - 1; j >= 0; --j) {
                temp = inter(line[j], all[i]);
                if (p.empty() || (cmp(temp, p.back()) > 0)) {
                    break;
                }
                p.pop_back();
                line.pop_back();
            }
            p.push_back(temp);
            line.push_back(all[i]);
        }
    }
    
}
                
double solution(vector<int> &A, vector<int> &B) {
    // write your code here...
    vector<pair<int,int> > all;
    int i,j,n = A.size();
    for (i = 0; i < n; ++i) {
        all.push_back(make_pair(A[i],B[i]));
    }
    sort(all.begin(),all.end());
    vector<pair<int,int> > U, UP;
    make(all, UP, U);
    for (i = 0; i < n; ++i) {
        all[i].first = -all[i].first;
        all[i].second = -all[i].second;
    }
    reverse(all.begin(), all.end());
    vector<pair<int,int> > D, DP;
    make(all, DP, D);
    bool inf = true;
    int c;
    double r = 0,may;
    for (i = j = 0;;) {
        bool infi = (i >= UP.size()), infj = (j >= DP.size());
        if (infi) {
            if (infj) {
                break;
            }
            else {
                c = 1;
            }
        }
        else if (infj) {
            c = -1;
        }
        else {
            c = cmp(UP[i], DP[j]);
        }
        double k = U[i].first + D[j].first, b = U[i].second + D[j].second;
        if (c > 0) {
            may = k * DP[j].first / DP[j].second + b;
            ++j;
        }
        else if (c == 0) {
            may = k * DP[j].first / DP[j].second + b;
            ++i;
            ++j;
        }
        else {
            may = k * UP[i].first / UP[i].second + b;
            ++i;
        }
        if (inf || (r > may)) {
            inf = false;
            r = may;
        }
    }
    if (inf) {
        r = U[0].second + D[0].second;
    }
    return r;
}



评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值