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