《挑战程序设计竞赛》3.1.3 二分搜索-查找第k大的值 POJ3579 3685

POJ3579

http://poj.org/problem?id=3579

题意

给出n个数,要求将这n个数两两相减,把这些相减得到的数排序后,输出位置在中间的那个数。

思路

如果两两相减再排序复杂度太高,肯定超时了,二分法求解复杂度将大大降低。
枚举最中间的那个数,然后判断一下相减得到的数有多少个大于等于枚举的数。
如何判断上面所说的那句呢,其实不用把每个数相减,只需要排序一下,然后将当前这个数 + 枚举的那个数,然后在数组中找到大于等于这个数的第一个位置(lower_bound()),再用n减去那个数的位置就可以知道有多少个相减的结果会大于等于这个数了。
最后只需要判断一下大于等于这个枚举数的数有几个就可以了,当这个数大于等于(n * (n - 1) / 2 / 2 - 1)就表示这个枚举数是小于或者等于这个最中间值的(等于的情况就是最中间值能有多个数相减得到)。

代码

Source Code

Problem: 3579       User: liangrx06
Memory: 380K        Time: 610MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 100000;

typedef long long LL;

LL n, m;
int a[N];

bool check(int mid)
{
    LL cnt = 0;
    for (int i = 0; i < n; i ++)
        cnt += (lower_bound(a, a+n, mid + a[i]) - a-i-1);
    return cnt < m;
}

int main(void)
{
    while (cin >> n) {
        m = (n*(n-1)/2+1)/2;
        for (int i = 0; i < n; i ++)
            scanf("%d", &a[i]);
        sort(a, a+n);

        int lb = 0, ub = a[n-1]-a[0];
        while (ub - lb > 1) {
            int mid = (lb + ub) / 2;
            if (check(mid)) lb = mid;
            else ub = mid;
        }
        printf("%d\n", lb);
    }

    return 0;
}

POJ3685

http://poj.org/problem?id=3685

题意

给出一个矩阵,这个矩阵中各个元素的值由给定的公式确定。即:i2 + 100000 ×i + j2 - 100000 × j + i × j。求第m小的数。

思路

从该式子可以看出,结果随着i单调递增,但对j呢?求导后可发现不是单调递增的,所以找规律就是错的。求第k大元素嘛,还是考虑二分。二分的关键是怎么去判断。因为j无单调性可言,那么就枚举j,二分找出每列的小于mid的个数cntx,和小于等于mid的个数cnty。总复杂度就是logINF*n*logn,是可以接受的。然后根据相应逻辑判断即可。这道题,就是二分里再套二分,还是蛮有意思的。
几个值得注意的地方:
(1)不能事先存储全部矩阵的值,全存储会造成超内存;
(2)如果在查找有序列(第i列)时将其全部计算然后查找,复杂度就升高了,会造成超时;
(3)变量有一部分需要用long long来存储,但若全部用long long会增加不必要的时间(4000多ms),合理的根据变量范围设定变量类型能够更好的节约时间(900多ms)。

代码

Source Code

Problem: 3685       User: liangrx06
Memory: 236K        Time: 922MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;

LL n, m;

LL f(LL i, LL j)
{
    return i*i + 100000*i + j*j - 100000*j + i*j;
}

LL my_lower_bound(int j, int lb, int ub, LL key)
{
    while (ub - lb > 1) {
        int mid = (ub + lb) / 2;
        LL x = f(mid, j);
        if (x >= key) ub = mid;
        else lb = mid;
    }
    return ub;
}

bool check(LL mid)
{
    LL cnt = 0;
    for (int j = 1; j <= n; j ++) {
        cnt += (my_lower_bound(j, 0, n+1, mid) - 1);
    }
    return cnt < m;
}

int main(void)
{
    int t;
    cin >> t;
    while (t--) {
        cin >> n >> m;
        LL lb = -100000*n, ub = n*n + 100000*n + n*n + n*n;
        while (ub - lb > 1) {
            LL mid = (lb + ub) / 2;
            if (check(mid)) lb = mid;
            else ub = mid;
        }
        printf("%lld\n", lb);
    }

    return 0;
}

你可能感兴趣的:(二分搜索,poj,挑战程序设计竞赛,查找第k大的值)