【问题标题】:Binary search given slightly inaccurate results给出稍微不准确的结果的二分搜索
【发布时间】:2017-06-11 08:44:45
【问题描述】:

我要解决的问题如下:我得到 N 个 1 厘米宽和长 C 的矩形纸条。我需要在切割带的面积总和等于 A 的高度切割条带。您可以在下面看到一个示例,其中 N = 5,条带的长度分别为 5、3、6、2 和 3 厘米,A = 3 厘米,其中切口是4厘米。

请注意,我在这里寻找红色区域。

输入如下。每种情况的第一行都以两个整数 N (1 ≤ N ≤ 10^5) 和 A (1 ≤ A ≤ 10^9) 分别表示条带的数量和预期的结果面积。下一行包含 N 个整数,表示每个条带的长度 C_i (1 A = C = 0 结尾,不应处理。

对于每个测试用例,输出单行,切割的高度H,必须做到切割条的面积之和等于A强>cm²。打印带有 4 位小数的答案。如果不需要切割,则输出“:D”,如果不可能,则输出“-.-”。

这个问题可以找到here

我解决这个问题的想法是使用二分搜索,我在条带的中间选择一个高度,并根据我的切割是太高还是太低来使其更大或更小。我对问题的实现如下:

#include <iostream>
#include <vector>
#include <iomanip>
#include <algorithm>

using namespace std;

int main(){
    vector<int> v;  // Vector that holds paper heights
    int n;          // Number of papers
    double h,       // Height of the cut
           sum,     // Area sum
           min_n,   // Minimum height for cut to happen
           max_n,   // Maximum height for cut to happen
           a;       // Desired final area

    // Set desired output
    cout << fixed << setprecision(4);

    /* Get number of papers and desired area,
       terminates if N = A = 0
    */
    while(cin >> n >> a && (n||a)){
        v.resize(n); // Resize vector to fit all papers
        // Get all paper sizes
        for(int i=0;i<n;i++){
            cin >> v[i];
        }
        /* Sort the vector in decreasing order to
           simplify the search
        */
        sort(v.begin(),v.end(),greater<int>());
        max_n = v[0]; // Largest possible cut is at the height of the largest paper
        min_n = 0; // Smallest possible cut is at the base with height 0

        // Iterate until answer is found
        while(true){
            // Initialize cut height as the average of smallest and largest cut sizes
            h = (min_n + max_n)/2;

            /* The area sum is equal to the sum of the areas of each cut, which is
               given by the height of the paper minus the cut height. If the cut is
               higher than the paper, the cut has area 0.
            */
            sum = 0;
            // Using mascoj sugenstion, a few changes were added
            int s; // Temporary variable to hold number of time h is subtracted
            for(int i=0; i<n;i++){
                if(v[i] <= h) break; // From here onward cut area is 0 and there is no point adding
                sum += v[i]; // Removed the subtraction inside of the for loop
                s++; // Count how many paper strips were used
            }
            sum -= h*s // Subtracts the area cut from the s paper strips

            // If the error is smaller than the significant value, cut height is printed
            if(std::abs(sum-a) < 1e-5){
                // If no cut is needed print :D else print cut height
                (h < 1e-4 ? cout << ":D" << endl : cout << h << endl);
                break;
            }
            // If max_n is "equal" to min_n and no answer was found, there is no answer
            else if(max_n - min_n < 1e-7){
                cout << "-.-" << endl;
                break;
            }
            // Reduces search interval
            sum < a ? max_n = h : min_n = h;
        }
    }
    return 0;
}

问题是,在提交我的答案后,我不断收到 10% 的错误。该网站有一个工具,用于将您的程序输出与预期输出进行比较,因此我运行了一个包含 1000 多个随机生成的测试用例的测试文件,当我比较两者时,我在小数点后 4 位出现舍入错误,不幸的是,我没有没有文件也没有脚本来为我生成测试用例了。我尝试将可接受的错误更改为较小的错误,但这没有用。我似乎找不到错误,你们中有人知道发生了什么吗?

ps:虽然问题描述上没有说,但是你可以得到分数作为高度的削减

【问题讨论】:

  • 尝试将abs 替换为std::abs,以确保您不会不小心从cstdlib 调用int abs(int)
  • 谢谢芽,但仍然不是这样:/
  • “打印带 4 位小数的答案” 可以表示舍入或截断。所以也许在打印值之前尝试调用std::fesetround(FE_DOWNWARD);
  • 一种风格建议:从 for 循环中删除 break 并将条件移动到循环本身。 for (i=0; i&lt;n &amp;&amp; v[i]&gt;h; i++)
  • 您说您遇到了 10% 的错误,但您也说这是小数点后 4 位的错误,即 0.01%。是哪个?

标签: c++ c++11 search binary-search


【解决方案1】:

可能是你的问题,也可能不是:这一行正在加剧浮点错误:sum += v[i]-h;

浮点数只有如此精确,并且在更大的总和上叠加此错误。我会尝试在h 上使用乘法,然后从适用长度的总和中减去它。应该在双精度格式的范围内,所以我不会担心超出格式。

【讨论】:

  • 还是不是这样,我只是根据你的建议修改了代码,但10%的错误仍然存​​在
【解决方案2】:

不确定是否理解您的算法,但我认为使用地图而不是矢量可以更简单地完成。

在以下示例中,映射 mp 记住给定长度(键)有多少(值)条。

地图的一个优势是有序的。

接下来,您可以查看您需要节省多少(而不是 cat)并计算从零开始的削减水平,适当时加 1,必要时加小数。

希望下面的例子能有所帮助

#include <map>
#include <iomanip>
#include <iostream>

int main()
 {
   int                         n;
   int                         n2;
   int                         v;
   int                         cut;
   int                         a;
   std::map<int, std::size_t>  mp;
   long long int               sum;
   long long int               ts;


   std::cout << std::fixed << std::setprecision(4);

   while( (std::cin >> n >> a) && ( n || a ) )
    {
      mp.clear();

      n2  = 0;
      sum = 0LL;

      for ( auto i = 0 ; i < n ; ++i )
       {
         std::cin >> v;

         if ( v > 0 )
          {
            sum += v;

            ++mp[v];
            ++n2;
          }
       }

      // mp is a map, so the values are ordered

      // ts is "to save"; sum of lenghts minus a
      ts  = sum - a;

      // cut level
      cut = 0;

      // while we can add a full cm to the cut level
      while ( (ts > 0LL) && (n2 > 0) && (ts >= n2) )
       {
         ++cut;

         ts -= n2;

         if ( cut >= mp.cbegin()->first )
          {
            n2 -= mp.cbegin()->second;

            mp.erase(mp.cbegin());
          }
       }

      if ( (ts == 0LL) && (cut == 0) )
         std::cout << ":D" << std::endl; // no cut required (?)
      else if ( n2 == 0 )
         std::cout << "-.-" << std::endl; // impossible (?)
      else
         std::cout << (cut + double(ts) / n2) << std::endl;
    }
 }

p.s.:请注意 a 在您链接的页面中被定义为整数。

【讨论】:

  • 我不能使用地图,没有什么能阻止我拥有 2 篇或更多篇相同长度的论文
  • @JoãoAreias - 我使用地图正是因为您可以拥有相同长度的论文:关键是长度,值是具有该长度的论文数量。如果您愿意,可以使用多重集,但我认为地图更好。
  • 对不起,我没有关注代码中发生的事情,您能否稍微评论一下,以便我更好地理解?
  • @JoãoAreias - 我添加了几个 cmets;查看cut(切割级别;从零开始,递增1.0 cm,直到可以打印并打印分数)和n2(比cut 更长的条带数量)。抱歉,我解释得很糟糕
  • 对不起,我认为我没有很好地解释我的问题,他们也没有很好地说明,但你实际上可以得到分数。您会看到,当您运行不同的测试用例时,这就是我通过二进制搜索来解决它的原因。不过还是谢谢你。
猜你喜欢
  • 1970-01-01
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-20
  • 1970-01-01
相关资源
最近更新 更多