【问题标题】:Divide and Conquer algorithm in C++C++中的分而治之算法
【发布时间】:2017-06-08 20:11:41
【问题描述】:

网上某评委有这么一个问题,我不知道如何录取。

问题是这样的,第一行包含两个数字

N (0 < N < 2^18) 
M (0 < M < 2^20)

第二行包含N 数字

ai (0 < ai < 2^40)

问题是有多少X是满意的:

M = floor(X/a1) + floor(X/a2) + ... + floor(X/an)

我的幼稚解决方案:

#include<bits/stdc++.h>
using namespace std;

long long n,m,i,j,haha,sum;
int main()
{
    cin >> n >> m;
    haha = 0;
    long long ar[n+5];
    for(i = 0; i < n; i++) cin >> ar[i];
    sort(ar,ar+n);
    for(i = ar[0]+1; i < m*ar[0]; i++){
        sum = 0;
        for (j = 0; j < n; j++) sum += i/ar[j];
        if (sum == m) haha += 1;
        else if (sum >= m) break;
    }
    cout << haha << endl;
}

更新1: 我的二分查找解决方案(仍然没有超过时限):

#include<bits/stdc++.h>
using namespace std;

long long n,m,i,l,r,mid,ans,tmp,cnt,haha;
long long ar[2621440];
long long func(long long x){
    haha = 0;
    for (i = 0; i < n; i++) haha += x/ar[i];
    return haha;
}

int main()
{
    cin >> n >> m;
    for(i = 0; i < n; i++) cin >> ar[i];
    sort(ar,ar+n);
    l = ar[0];
    r = ar[0]*m;
    mid = (l+r)/2;
    tmp = func(mid);
    while (tmp != m){
        mid = (l+r)/2;
        tmp = func(mid);
        if (l == r) break;
        if (tmp < m) l = mid+1;
        else if (tmp > m) r = mid-1;
        else break;
    }
    ans = 0;
    if (tmp == m) ans += 1;
    cnt = mid;
    while (func(cnt-1) == m){
        ans += 1;
        cnt -= 1;
    }
    cnt = mid;
    while (func(cnt+1) == m){
        ans += 1;
        cnt += 1;
    }
    cout << ans << endl;
}

【问题讨论】:

  • 能否提供问题链接?
  • @User_Targaryen 这个问题是用我的母语写的,来自当地的在线评委。证明链接无济于事。如果你真的想知道link.
  • @Aldihilmanr 如果问题应该由 D&C 解决,我认为这里唯一的选择是使用二进制搜索。谓词是“X 是否使这个表达式等于 M?”,如果不是,那么你沿着排序的 X 值向左或向右,如果是,你停下来检查 X 的邻域
  • @grek40 不起作用。例如N = 3 和M = 10。我们得到a1 = 1、a2 = 1 和a3 = 1。我们没有X 的可能性。
  • 然后显示您对二分搜索的尝试。讨论线性搜索是没有意义的。

标签: c++ algorithm divide-and-conquer


【解决方案1】:

更新

使用二进制搜索方法,这是我的新代码:

// compute X/ai sum
long long summarize(long long ar[], long long n, long long X)
{
    long long sum = 0;
    for (long long i = 0; i < n; i++)
    {
        sum += X/ar[i];
    }
    return sum;
}

bool get_range(long long ar[], int n, int m, pair<long long, long long>& range)
{
    long long sum = 0;
    long long x;
    // reduce range
    while (range.first < range.second)
    {
        x = (range.first + range.second) / 2;

        sum = summarize(ar, n, x);
        if (sum < m)
        {
            range.first = x + 1;
        }
        else if (sum > m)
        {
            range.second = x;
        }
        else if (x == range.first)
        {
            return true; // single element
        }
        else
        {
            break;
        }
    }

    if (sum != m)
    {
        return false;
    }

    // check surroundings for lower / upper bound.
    sum = summarize(ar, n, range.first);
    if (sum != m)
    {
        auto r1 = make_pair(range.first + 1, x);
        if (get_range(ar, n, m, r1))
        {
            range.first = r1.first;
        }
        else
        {
            range.first = x;
        }
    }
    sum = summarize(ar, n, range.second - 1);
    if (sum != m)
    {
        auto r2 = make_pair(x + 1, range.second - 1);
        if (get_range(ar, n, m, r2))
        {
            range.second = r2.second;
        }
        else
        {
            range.second = x + 1;
        }
    }

    return true;
}


int main()
{
    int n, m;
    cin >> n >> m;
    long long *ar = new long long[n];
    long long ar_min = LLONG_MAX;
    for(long long i = 0; i < n; i++)
    {
        cin >> ar[i];
        ar_min = min(ar[i], ar_min);
    }
    // initial range of possible X values
    auto range = make_pair(m / (ar_min * n), m * ar_min);
    if (get_range(ar, n, m, range))
    {
        cout << (range.second - range.first) << endl;
    }
    else
    {
        cout << 0 << endl;
    }
}

核心功能是get_range 函数,它采用一个可能的范围([range.first, range.second),所以第二个是不是范围的一部分)并缩小范围,使范围内的所有元素都满足条件.它首先迭代地调整范围边界,直到范围的中间是结果的一部分,或者直到范围内没有结果。然后,如果有任何结果,它会递归检查找到的结果下方和上方的子范围,以检索整个结果范围的边界。

版本 1

您只处理大于零的正数。

M = floor(X/a1) + floor(X/a2) + ... + floor(X/an)

对于每个子项floor(X/a1),如果X1 &lt; X2,则有floor(X1/ai) &lt;= floor(X2/ai)。所以导致M的唯一可能的X值是那些floor(X1/ai) == floor(X2/ai)代表所有i(或所有ai)。

对于每个ai,对于某些k,这恰好是X1=k*aiX2=k*ai+(ai-1) 的范围。

这意味着,如果存在任何解决方案,对于某些0 &lt; k &lt;= m,X 值的范围将在k*min(ai)(k+1)*min(ai) 之间。

因此可能值得首先获取可能结果的范围,然后仅检查该范围内的各个值。

结果算法:

// compute X/ai sum
long long summarize(long long ar[], long long n, long long X)
{
    long long sum = 0;
    for (long long i = 0; i < n; i++)
    {
        sum += X/ar[i];
    }
    return sum;
}

int main()
{
    int n, m;
    cin >> n >> m;
    long long *ar = new long long[n];
    long long ar_min = LLONG_MAX;
    for(long long i = 0; i < n; i++)
    {
        cin >> ar[i];
        ar_min = min(ar[i], ar_min);
    }

    // lowest possible k
    long long k = m / (ar_min * n);
    // get the value k for a possible range of X values
    for (; k <= m; k++)
    {
        auto x = ar_min * (k + 1);
        long long sum = summarize(ar, n, x);
        if (sum > m)
        {
            break;
        }
    }
    long long X_min = k * ar_min, X_max = (k + 1) * ar_min;
    long long result = 0;
    // count possible X values
    for (long long x = X_min; x < X_max; x++)
    {
        long long sum = summarize(ar, n, x);
        if (sum == m)
        {
            ++result;
        }
        else if (sum > m)
        {
            break;
        }
    }

    cout << result << endl;
}

它比我最初预期的要复杂一些。我希望它仍然是某种改进。

【讨论】:

  • 不错的算法。但是您的算法比我使用二进制搜索的最新算法慢。是的,你的算法也没有超过时间限制,它甚至得到了更低的分数。
  • 是的,后来我发现标题“分而治之”是关键……正在更新,但还没有完成。
  • 是的。你的二进制搜索算法得到了与我的二进制搜索版本代码相同的分数。而且,你猜对了,它没有超过时间限制。
  • @Aldihilmanr 您是否有任何时间紧迫的示例测试用例或所有测试都适合您?
  • 不幸的是黑盒。
【解决方案2】:

我相信对此的预期解决方案是二分搜索。

定义f(x) = sum_i f(x/a_i)。不失一般性,假设a_i 是按递增顺序给出的。

很明显,

  • f(0) = 0 &lt; M
  • f(M*a_1) ≥ M
  • f(x) ≥ f(y) if x≥y

因此,您可以进行二分搜索以找到 x 的最小值,例如 f(x) = M,并将 start = 0end = M*a_1 作为二分搜索的初始限制。

要找到 x 的上限,请进行另一次二分搜索,或者只是循环遍历数组中的所有值以找到最小的 y,例如 floor(y/ai) &gt; floor(x/ai) 中的一些 i

【讨论】:

  • 分享你的二进制搜索代码,也许可以改进。
  • 问题不在于二分查找,而在于二分查找后的 while 循环。那需要的时间太长了。您应该使用二进制搜索来找到有效的 mid 的最低值,而不是在第一个实例中中断。并使用另一个二进制搜索找到第一个给出更大功能的值。
【解决方案3】:

使用以下代码使用两个二进制搜索(每个用于下限和上限)被接受(最终):

#include<bits/stdc++.h>
using namespace std;

long long n,m,i,l,r,mid1,mid2,ans,tmp,cnt,haha,k;
long long ar[26214400];
long long func(long long x){
    haha = 0;
    for (k = 0; k < n; k++) haha += x/ar[k];
    return haha;
}

int main()
{
    cin >> n >> m;
    for(i = 0; i < n; i++) cin >> ar[i];
    sort(ar,ar+n);
    l = ar[0];
    r = ar[0]*m;
    mid1 = (l+r)/2;
    tmp = func(mid1);
    while (l < r){
        mid1 = (l+r)/2;
        tmp = func(mid1);
        if (tmp < m) l = mid1+1;
        else if (tmp > m) r = mid1-1;
        else r = mid1-1;
    }
    mid1 = l; //lower bound
    l = ar[0];
    r = ar[0]*m;
    mid2 = (l+r)/2;
    tmp = func(mid2);
    while (l < r){
        mid2 = (l+r)/2;
        tmp = func(mid2);
        if (tmp < m) l = mid2+1;
        else if (tmp > m) r = mid2-1;
        else l = mid2+1;
    }
    mid2 = r; //upper bound
    while (mid1 <= mid2 and func(mid1) != m) mid1 += 1;
    while (mid2 >= mid1 and func(mid2) != m) mid2 -= 1;
    ans = mid2-mid1+1;
    cout << ans << endl;
}

【讨论】:

    猜你喜欢
    • 2020-09-24
    • 2013-02-02
    • 1970-01-01
    • 2012-01-01
    • 2013-02-03
    • 1970-01-01
    • 2013-01-12
    • 2019-06-03
    • 1970-01-01
    相关资源
    最近更新 更多