POJ 3685 Matrix 二分

一、题目大意

有一个N(1<=N<=1e5)行的矩阵,矩阵第i行和第j列的元素值为 i*i+100000*i-100000*j+j*j+i*j,给出一个M,我们要输出矩阵中第M大的元素。

二、解题思路

矩阵元素有25亿,暴力枚举肯定行不通,所以这里采用二分。

可以对第M大的元素进行二分算法,依据如下

设二分枚举的数字为mid
当矩阵中小于mid的元素数小于M时,增大mid
当矩阵中小于mid的元素数大于等于M时,减小mid

不断的去接近一个临界值limit,使得mid=limit时
矩阵中小于mid的元素数量小于M
当mid=limit+1时
矩阵中小于mid的元素数量大于等于mid
那么这个limit就是第M大的元素

然后思考如何去计算矩阵中小于mid的元素数量

我们可以一行一行的去考虑
当i固定时,矩阵元素的值与列的关系是一个开口向上的抛物线
对称轴是(100000-i)/2

那么可以认为对j∈[1,(100000-i)/2],曲线单调递减
j∈[(100000-i)/2+1,N]是单调递增

找出单调递减的序列小于mid的最大值位置p不难,二分即可
右边界-p + 1  就是第i行递减序列中小于mid的元素个数

找出单调递增序列中小于mid的最大值位置q也不难,二分即可
q - 左边界 + 1 就是第i行递增序列中小于mid的元素个数

然后递增序列和递减序列小于mid的元素的和,就是第i行小于mid的元素个数

不断循环,找到所有行小于mid的元素个数,求和即可

最终的和就是矩阵中小于mid的元素个数

最终复杂性是 log(n)*log(n)*n,是可行的

二分的最大最小值,可以提前写程序算出这个矩阵,找一下矩阵最大最小元素。

同时每一行需要考虑对称轴与N的大小关系,如果对称轴大于N,那么这一行没有单增序列

对称轴计算时就用整除就可以,因为整除结果+1之后一定也是大于对称轴的

三、代码

#include 
using namespace std;
typedef long long ll;
int N;
ll M;
bool compare(int i, int j, ll val)
{
    ll iLong = i;
    ll jLong = j;
    ll express = (iLong * iLong);
    express += (100000LL * iLong);
    express += (jLong * jLong);
    express -= (100000LL * jLong);
    express += (iLong * jLong);
    return express < val;
}
// 在矩阵的第i行,递增序列[low,high]中找出小于val的元素数量
ll lowerBoundAsc(ll val, int low, int high, int i)
{
    int left = low - 1;
    int right = high + 1;
    while (left + 1 < right)
    {
        int mid = (left + right) / 2;
        if (compare(i, mid, val))
        {
            left = mid;
        }
        else
        {
            right = mid;
        }
    }
    return left + 1 - low;
}
// 在矩阵第i行,递减序列[low,high]中找出小于val的元素数量
ll lowerBoundDesc(ll val, int low, int high, int i)
{
    int left = low - 1;
    int right = high + 1;
    while (left + 1 < right)
    {
        int mid = (left + right) / 2;
        if (compare(i, mid, val))
        {
            right = mid;
        }
        else
        {
            left = mid;
        }
    }
    return high + 1 - right;
}
bool judge(ll mid)
{
    ll count = 0;
    for (int i = 1; i <= N; i++)
    {
        int duiChenZhou = (100000 - i) / 2;
        count += lowerBoundDesc(mid, 1, min(N, duiChenZhou), i);
        if (N > duiChenZhou)
        {
            count += lowerBoundAsc(mid, duiChenZhou + 1, N, i);
        }
    }
    return count < M;
}
void binarySearch()
{
    ll left = -2499850000LL, right = 7500000001LL;
    while (left + 1 < right)
    {
        ll mid = (left + right) / 2LL;
        if (judge(mid))
        {
            left = mid;
        }
        else
        {
            right = mid;
        }
    }
    printf("%lld\n", left);
}
int main()
{
    int t = 0;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%lld", &N, &M);
        binarySearch();
    }
    return 0;
}

你可能感兴趣的:(算法)