挑战程序设计竞赛3.1习题:Matrix POJ - 3685

Given a N × N matrix A, whose element in the i-th row and j-th column Aij is an number that equals i2 + 100000 × i + j2 - 100000 × j + i × j, you are to find the M-th smallest element in the matrix.

Input

The first line of input is the number of test case.
For each test case there is only one line contains two integers, N(1 ≤ N ≤ 50,000) and M(1 ≤ M ≤ N × N). There is a blank line before each test case.

Output

For each test case output the answer on a single line.

Sample Input

12

1 1

2 1

2 2

2 3

2 4

3 1

3 2

3 8

3 9

5 1

5 25

5 10

Sample Output

3
-99993
3
12
100007
-199987
-99993
100019
200013
-399969
400031
-99939
这道题暴力肯定超时,不如来二分,怎么找到第K小呢?我们可以二分找出比一个值小的所有个数,他们的个数如果小于K的最大值即可。
我们怎么不暴力找到第K小呢?,我们通过对函数分别对i, j求偏导可以知道,只有自变量为i,j一定时函数才单调增,所以我们要遍历每列,但是每列中的元素都是单调递增的,我们就可以改用a[i][j]代表第j行,第i列的方法储存,然后用lower_bound找到每列第一个大于等于该值的指针,减去该列的首地址就是个数了,累加就行了,是不是很棒?
但是,int存不下,得long long,而且数组太大了,编译器都不给过!!!所以只能每列继续二分搜索,因为是单调的,且起始结尾的编号我们是知道的,就能二分搜索,只算每次mid的值,节约时间。
AC代码:
#include <stdio.h>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll INF = 0x3ffffffffffffff;
ll m, n;
ll cal(ll i, ll j)//记住i, j得long long否则万一i * j溢出int就错了
{
    return i * i + 100000 * i + j * j - 100000 * j + i * j;
}
bool check(ll x)
{
    ll cnt = 0;//个数有可能超int,因为50000 * 50000 = 2.5*10^9,而int最大约2.1*10^9
    for(int i = 1; i <= n; i++)
    {
        ll left = 0, right = n + 1;//采用左闭右开的方法
        while(right - left > 1)
        {
            ll mid = (right + left) / 2;
            if(cal(mid, i) < x)//left记录满足小于x的最大值
                left = mid;
            else
                right = mid;
        }
        cnt += left;
    }
    if(cnt < m)//满足小于x的个数小于m,说明小了或者刚好
        return true;
    else
        return false;
}
int main(void)
{
    int t;
    scanf("%d", &t);
    for(int i = 0; i < t; i++)
    {
        scanf("%lld %lld", &n, &m);
        ll left = -INF, right = INF;
        while(right - left > 1)
        {
            ll mid = (right + left) / 2;
            if(check(mid))
                left = mid;
            else
                right = mid;
        }
        printf("%lld\n", left);
    }
    return 0;
}

  

 
posted @ 2020-02-22 22:06  funforever  阅读(155)  评论(0编辑  收藏  举报