poj 3685

 /*
    给定n,有n*n个数,有aij=i * i + m * i + j * j - m * j + i * j
    n <= 50000
    m <= 100000
    求所有aij中第m大的数

    还是二分答案,先根据生成的式子,求出最大的和最小的数字,然后二分答案,验证
    我们发现,对于同列的数字,按照行数的增加,数字大小是递增的
    根据这个性质,就很容易验证是否存在>=n*n-m+1个数字>=val了

    看了题一下子就有想法了,结果却wa了一天...原因在于自作聪明地进行数学推论然后非要从同行中进行二分...最后才发现不满足递减性质...不过最后ac的时候还是很爽快的 
*/
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#define range(i,a,b) for (long long i=a;i<=b;i++)

using namespace std;

typedef long long ll;

const ll m = 1e5;

ll n;
ll M;

inline ll Int(ll i,ll j)
{
    return i * i + m * i + j * j - m * j + i * j;
}

//找到最小的i,满足>=val
ll findK(ll j,ll val)
{
    ll l(1),r(n);
    while(l+1<r)
    {
        int mid = (l+r)>>1;
        if (Int(mid,j) >= val)
            r = mid;
        else
            l = mid;
    }
    //返回>=val的数字的个数
    if (Int(l,j) >= val)
        return n-l+1;
    if (Int(r,j) >= val)
        return n-r+1;
    return 0;
}

bool test(ll val)
{
    ll ans(0);
    //返回每一行的>=val的数量
    range(c,1,n)
    {
        ans += findK(c,val);
    }

    return ans >= (ll)n*n - M+1;
    //必须要有>=n*n-M+1的数量>=ans,ans才有可能是答案
}

int main()
{
    int t;
    cin>>t;

    range(c,1,t)
    {
        cin>>n>>M;
        ll l(-n*m),r(n*n*3 + n*m);
        while(l+1<r)
        {
            ll mid = (l+r)/2;
            if (mid == 5101786214)
            {
                cout<<"";
            }
            if (test(mid))
            {
                l = mid;
            }
            else
            {
                r = mid;
            }
        }
        if (test(r))
        {
            cout<<r<<endl;
        }
        if (test(l))
        {
            cout<<l<<endl;
        }

    }
    return 0;
}

 

你可能感兴趣的:(poj 3685)