xdu1068暨2013陕西省赛C题题解

xdu1068暨2013陕西省赛C题题解

题意

知道两个数列M和F,每次从M中选择一个人,和从F中选择的一个人配对,结果是Mi*Fj,请问所有配对情况中第k大的情况是多少。

笺释

先对M和F从小到大排序,显然最小的情况是F[1]*M[1],最大的情况是F[n]*M[m],在这个区间内二分。
确定答案x时,若x使得所有配对情况中大于x的数目大于等于K的话,就意味着x太小啦,我们就将mid向右移来增大x,否则减小x。
确定x使得所有配对情况中大于x的数目大于等于k的还是小于k,用到了很厉害的一个性质,可以先回忆以下xdu第一周训练题目B题,那个题我们从小到大枚举数字,最小的数字可以与其余数字组成n-1个情况使得答案为最小的数字,但是这个题因为答案是两数乘积,所以不能单纯地由某一个数字确定,我们同样是从小到大枚举M中的数字Mi,然后F中的数字从大到小枚举Fj,这样就造成了:若j之前的满足Mi*Fj大于x,那么当i增大的时候,Mi增大,j之前的一定也满足,就不需要再枚举一遍,这样就将复杂度降低为了n+m而不是n*m。

具体说感觉也说不太清楚,结合代码理解吧。

完整代码

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
int t,A,B;
long long M[10005],F[10005];
int  K;
int check(long long x)
{
    int ret=0;
    int j=B;
    for(int i=1;i<=A;i++)
    {
        for(;(j>=1&&(M[i]*F[j])>x);j--);
        ret+=B-j;
    }
    return ret;
}
ll solve()
{
    ll l=M[1]*F[1];
    ll r=M[A]*F[B];
    while(l<=r)
    {
        ll mid=(l+r)/2;
        //printf("A %lld %lld
",l,r);
        int tem=check(mid);
       // printf("B %lld
",tem);
        if(tem>=K)
        {
            l=mid+1;
        }
        else
        {
            r=mid-1;
        }
    }
    return r+1;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d %d",&A,&B,&K);
        for(int i=1;i<=A;i++)
        {
            scanf("%lld",&M[i]);
        }
        for(int i=1;i<=B;i++)
        {
            scanf("%lld",&F[i]);
        }
        sort(M+1,M+1+A);
        sort(F+1,F+1+B);
        printf("%lld
",solve());
    }
}
原文地址:https://www.cnblogs.com/SoniciSika/p/9034205.html