【问题标题】:Using binary search to find k-th largest number in n*m multiplication table使用二分查找在 n*m 乘法表中查找第 k 个最大数
【发布时间】:2016-02-01 14:03:23
【问题描述】:

所以,我正在尝试解决这个问题:http://codeforces.com/contest/448/problem/D

Bizon the Champion 不仅迷人,而且非常聪明。

当我们中的一些人在学习乘法表时,Bizon the Champion 以他自己的方式玩得很开心。 Bizon the Champion画了一个n × m的乘法表,其中第i行j列的交点上的元素等于i·j(表的行和列从1开始编号)。然后他被问到:表中第k大的数字是多少? Bizon the Champion 总是正确而迅速地回答。你能重复他的成功吗?

考虑给定的乘法表。如果你把表中的所有n·m个数按非递减的顺序写出来,那么你写出的第k个数称为第k个最大数。

输入 单行包含整数 n、m 和 k (1 ≤ n, m ≤ 5·105; 1 ≤ k ≤ n·m)。

输出 打印 n × m 乘法表中第 k 个最大的数。

我所做的是,我应用从 1 到 n*m 的二进制搜索来寻找恰好小于它的 k 元素的数字。为此,我编写了以下代码:

using namespace std;
#define ll long long
#define pb push_back
#define mp make_pair
ll n,m;
int f (int val);
int min (int a, int b);
int main (void)
{
    int k;
    cin>>n>>m>>k;
    int ind = k;
    ll low = 1LL;
    ll high = n*m;
    int ans;
    while (low <= high)
    {
        ll mid = low + (high-low)/2;
        if (f(mid) == k)
            ans = mid;
        else if (f(mid) < k)
            low = mid+1;
        else
            high = mid-1;
    }
    cout<<ans<<"\n";
    return 0;

}

int f (int val)
{
    int ret = 0;
    for ( int i = 1; i <= n; i++ )
    {
        ret = ret + min(val/i,m);
    }
    return ret;
}

int min (int a, int b)
{
    if (a < b)
        return a;
    else
        return b;
}

但是,我不知道为什么,但这对测试用例给出了错误的答案:

input
2 2 2
output
2

我的输出是 0

我正在学习二分搜索,但我不知道这个实现哪里出了问题。任何帮助将不胜感激。

【问题讨论】:

  • 这不是关于搜索,第 k 个最大的数是从末尾开始写入的第 k 个元素。您可以使用模运算和标准运算直接计算它。
  • 如果你能准确找到答案,你需要一个break,否则这种情况将是一个无限循环。但根据您报告的症状,这不是唯一的错误。
  • @Dese,但是你怎么写这样的元素呢?复杂度将大于 O(n^2) 并且不支持给定的限制。
  • @JSF,即使破解了,好像也不管用。
  • @hans 第一个最大的元素是 n*m,第二个最大的元素是 n*(m-1),第三个是 n*(m-3)....行首:n,则根据定义为 (n-1)*m。 (参见 2 X 3 乘法表的例子,如果你真的在谈论最大的元素,那么第 4 个是 2: 6 4 3 2 但它们的定义是关于书写顺序的,所以答案是 3)。我说的是找到一个公式,使得结果 = f(n,m,k)。

标签: c++ algorithm binary-search


【解决方案1】:

忽略二进制搜索不是最快的方法这一事实,您仍然想知道它为什么不正确。

首先要非常清楚你想要什么以及你的 f 返回什么:

寻找恰好比它少 k 个元素的数。

不!您正在寻找 k 个元素小于 或等于 的最小数。并且您的函数f(X) 返回小于或等于 X 的元素的计数。

所以当 f(X) 返回一个太小的值时,你知道 X 必须至少大 1,所以low=mid+1 是正确的。但是当 f(X) 返回的值太大时,X 可能是完美的(可能是一个元素在表中出现多次)。相反,当 f(X) 返回完全正确的数字时,X 可能仍然太大(X 可能是在表中出现零次的值)。

所以当 f(X) 不太小时,你能做的最好的就是high=mid 而不是high=mid-1

while (low < high)
{
    ll mid = low + (high-low)/2;
    if (f(mid) < k)
        low = mid+1;
    else
        high = mid;
}

注意低永远不会>高,所以当它们相等时停止,我们不会试图在途中抓住ans。反而在最后低==高==答案

比赛规定时限为 1 秒。在我的电脑上,你的代码在不到一秒的时间内就解决了最大尺寸问题。但我不确定评判电脑有那么快。

编辑:int 对于问题的最大大小来说太小了,所以你不能从 f:
返回 int n、m、i 分别适合 32 位,但 f() 的输入和输出以及 k、ret、low 和 high 都需要保存最大为 2.5e11 的整数

【讨论】:

    【解决方案2】:
    import java.util.*;
    public class op {
        static int n,m;
        static long k;
        public static void main(String args[]){
            Scanner s=new Scanner(System.in);
            n=s.nextInt();
            m=s.nextInt();
            k=s.nextLong();
            long start=1;
            long end=n*m;
            long ans=0;
            while(end>=start){
                long mid=start+end;
                mid/=2;
                long fmid=f(mid);
                long gmid=g(mid);
                if(fmid>=k && fmid-gmid<k){
                    ans=mid;
                    break;
                }
                else if(f(mid)>k){
                    end=mid-1;
                }
                else{
                    start=mid+1;
                }
            }
            System.out.println(ans);
    
        }
        static long f (long val)
        {
            long ret = 0;
            for ( int i = 1; i <= n; i++ )
            {
                ret = ret + Math.min(val/i,m);
            }
            return ret;
        }
        static long g (long val)
        {
            long ret = 0;
            for ( int i = 1; i <= n; i++ )
            {
                if(val%i==0 && val/i<=m){
                    ret++;
                }
            }
            return ret;
        }
        public static class Pair{
            int x,y;
            Pair(int a,int b){
                x=a;y=b;
            }
        }
    }
    

    【讨论】:

    • 请解释一下您的解决方案。 OP 做错了什么?
    猜你喜欢
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    • 2018-03-30
    • 2016-05-15
    • 1970-01-01
    相关资源
    最近更新 更多