链接:传送到老年POJ
题意:
有一个 N ∗ N ( N ≤ 5 ∗ 1 0 4 ) N*N(N \leq 5*10^4) N∗N(N≤5∗104)的矩阵, ( i , j ) (i, j) (i,j)位置的元素值为$ i^2 + 10^5 * i + j^2 - 10^5 * j + i * j , 求 这 个 矩 阵 里 的 第 ,求这个矩阵里的第 ,求这个矩阵里的第M$大元素。
思路:
考试的时候没有想到,玄学二分加枚举再二分。
首先在矩阵里发现单调性(行啊列啊斜着啊各种胡乱找规律)。对于这个元素值的表达式,一个有两个变量的二次多项式,可以想着把某一个变量看成常量,然后对于一个变量就比较好分析了。
所以首先我们看某一行,即把i看成常数: j 2 + ( i − 1 0 5 ) ∗ j + i 2 + 1 0 5 ∗ i j^2+(i-10^5)*j+i^2+10^5*i j2+(i−105)∗j+i2+105∗i,然而这个二次函数在j大于0的情况下并没有什么性质,因为对称轴在y轴右边,所以没单调性。
再看一列,同理: i 2 + ( i + 1 0 5 ) ∗ i + j 2 − 1 0 5 ∗ j i^2+(i+10^5)*i+j^2-10^5*j i2+(i+105)∗i+j2−105∗j,单调性出现了,因为对称轴在y轴左边。所以在某一列中寻找小于等于某个值的时候,就可以二分了。
然后想如何用上这个单调性。可以先二分答案,对于这个答案,求出每一列小于等于他的数的个数,数量等于M就得到答案啦。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll T, n, m;
inline ll Cal(ll x, ll y)
{
return x*x+x*1e5+y*y-y*1e5+x*y;
}
inline ll Count(ll x, ll y)
{
ll l, r, mid, ans = 0;
l = 1; r = n;
while (l <= r){
mid = (l+r)>>1;
if (Cal(mid, x) <= y)
ans = mid, l = mid+1;
else
r = mid-1;
}
return ans;
}
inline ll Check(ll sta)
{
ll ret = 0;
for (ll j = 1; j <= n; j++)
ret += Count(j, sta);
return ret;
}
ll Gao(ll m)
{
ll l, r, mid, ans;
l = -8e9+7;
r = 8e9+7;
while (l <= r){
mid = (l+r)>>1;
if (Check(mid) >= m)
ans = mid, r = mid-1;
else l = mid+1;
}
return ans;
}
int main()
{
// cout << log2(1e10)*log2(5e4)*5e4 << endl;
// ll maxn = -1e18+10, minn = 1e18+10;
// for (int i = 1; i <= 5e4; i++)
// maxn = max(maxn, Cal(5e4, i)), minn = min(minn, Cal(1, i));
// cout << maxn << " " << minn << endl;
scanf("%lld", &T);
while (T--){
scanf("%lld%lld", &n, &m);
printf("%lld\n", Gao(m));
}
return 0;
}