挑战程序设计竞赛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;
}

浙公网安备 33010602011771号