将数组划分为k个contiguos分区,使得最大分区的总和最小

时间:2016-09-24 07:47:32

标签: c++ arrays algorithm dynamic-programming subset-sum

这里,最大和子集是给出最大和的k个子集之一 例如:arr = [10,5,3,7]和k = 2 在k子集中划分arr的可能方法是{10,[5,3,7]},{[10,5],[3,7},{[10,5,3],7}和{[10, 5],[3,7}是最佳的。 编辑:它相当于 https://www.codechef.com/DI15R080/problems/MINMAXTF

6 个答案:

答案 0 :(得分:5)

假设您知道答案是 x ,这意味着最大子集的总和等于 x 。您可以通过贪婪算法 O(n)来验证此假设。 (从左到右遍历数组并选择项目,直到该子集的总和低于 x )。现在,您可以在 x 上进行二进制搜索,并找到 x 的最小值。该算法的复杂性为 O(nlogn)

答案 1 :(得分:3)

这是二进制搜索样本空间的示例。

int min_max_sum(std::vector<int> & a, int K) {        

    int n = a.size();    
    long long high = 0, low = 0, mid = 0;

    for (int i = 0; i < n; ++i) {
        high += a[i];
        low = max(a[i], low);
    }

    while(low <= high) {
        mid = (low+high)/2;

        long long part_sum = 0;
        int parts = 1;
        for (int i = 0; i < n; ++i) {
            if (part_sum + a[i] > mid) {
                part_sum = 0;
                parts++;
            } else {
                part_sum += a[i];
            }
        }

        // if no. of parts in less than (or equal to) K then mid needs to (,or can) be more constrained by reducing upper limit
        if (parts <= K) {
            high = mid - 1;
        } else { 
            low = mid + 1;
        }
    }

    return mid;
}

复杂性:O(n log(sum(array)))。

但由于logrithms比线性指数好,所以这种复杂性非常好。

最坏情况复杂度:O(n log(INT_MAX * n))= O(32 n + n log(n))= O(n log(n))。

答案 2 :(得分:1)

让我们从一个例子开始。假设N = 5且 k = 3(假设分割后将有三个部分)。让我们的数组为{1,2,8,4,9}。我们可以观察到,如果 k 等于1,那么最大分区的总和将是总和(所有数组元素),即24,如果 k = 5,则总和最大分区将是max(所有数组元素),即9.现在,我们可以观察到随着k的增加,最大分区的最小值之和减小。我们的算法将在二元搜索的帮助下完成。但是怎么做?????通过查看问题 - 我们可以看到,我们必须找到引起问题感觉的最小值,例如“FFFFTTTTTTT”,我们必须找到第一个T.我们将做同样的事情,但将采取这样做的一个函数的帮助。(从这里并行查看代码和答案)..当提供最大分区的最小总和时,辅助函数将找到K的值。我们将初始化low = max(所有数组元素),这里low = 9和high = sum(所有数组元素),即high = 24.因此,mid = 16,所以,对于min_max_dist = 16,我们的辅助函数将返回所需的k数。如果是k&gt; K ,这意味着我们的min_max_dist可以容纳更多的值。所以,我们将低值增加到mid + 1,如果k <= K ,这意味着在较少数量的分区中,我们可以实现min_max_dist,但我们可以做更多的分区,因此我们可以将高值减少到mid.So,我们的代码将在我们的示例中执行以下方式: -

    low           high               mid       number_of_k
    9             24                 16         9
    9             16                 12         2
    9             12                 10         4
    11            12                 11         3

最后我们的结果 - &gt; low = 11将被返回..

    #include <bits/stdc++.h>
    #define ll long long int
    using namespace std;

    ll number_of_k(ll arr[],ll n,ll minimum_max__dist){
       ll sum=0;
       ll k=1;
       for(ll i=0;i<n;i++){
          sum+=arr[i];
         if(sum > minimum_max__dist){
           sum=arr[i];
            k++;
          }
      }
    return k;
   }

    ll Max(ll arr[], ll n)
    {
       ll max1 = -1;
       for (ll i = 0; i < n; i++)
          if (arr[i] > max1)
              max1 = arr[i];
      return max1;
    }

    ll Sum(ll arr[], ll n)
    {
      ll sum = 0;
       for (int i = 0; i < n; i++)
       sum += arr[i];
       return sum;
     }

       ll min_max_bin_search(ll arr[],ll n,ll k){
         ll low=Max(arr,n);
         ll high=Sum(arr,n);
         ll mid;
while(low<high){
     mid=low+(high-low)/2;
    if(number_of_k(arr,n,mid)<=k)
       high=mid;
    else
        low=mid+1;
     }
  return low;
  }


 int main()
 {
      ll n,k;
       cin>>n>>k;
       ll arr[n];
       for(ll i=0;i<n;i++)cin>>arr[i];
       cout<<min_max_bin_search(arr,n,k)<<endl;

   return 0;
 }

该算法的时间复杂度为 - > O(nlogn)

阅读关于二元搜索的这篇文章 - &gt; https://www.topcoder.com/community/data-science/data-science-tutorials/binary-search/ 和 解决这个问题 - &gt; http://www.spoj.com/problems/AGGRCOW/

答案 3 :(得分:1)

您可以在这里找到有关基于动态编程的解决方案的出色文章:https://www.geeksforgeeks.org/painters-partition-problem/和 它的复杂度为O(k * N ^ 2)。

要获得更好的解决方案,可以使用其他人建议的二进制搜索方法。您可以在此处找到使用二进制搜索的更详细的解决方案:https://www.geeksforgeeks.org/painters-partition-problem-set-2/,其复杂度为O(NlogN)

答案 4 :(得分:0)

这可以使用动态编程解决:

让我们首先将DP[n,m]定义为将子阵列C[1..n]划分为m部分的最佳解决方案。每个部分至少有一个元素。

DP[n,1] = sum(C1:Cn)
DP[n,n] = max(C1:Cn)
DP[n,m] = min( sum(Ck:Cn) + DP[k-1,m-1] )
          where k goes from m to n

<强>解释
DP[n,1] - 基本情况,当分区数为1时,只有一种方式 - 所有元素都留下(从1到n)。
DP[n,n] - 只要分区数等于数组中剩余的元素数,就只有一种合法的方法来划分它 - 每个元素在不同的分区中,所以具有最大总和的分区是最大元素在数组中。
DP[n,m] - 这是主要解决方案。我们不确切知道下一个分区有多少元素,所以我们需要检查所有选项并从中获得最小值。

答案 5 :(得分:0)

这个部门只是一个暴力问题。您必须专注于最小化的功能。所以你必须尽量减少与平均值的偏差。

int sum_arr(int *arr,int size)
{
    int res=0;
    while (size-->0)
       res+=*arr++;
   return res;
}
int   minimize( const int *array, int size)
{
    int i,j,cur_i;
    double dev, cur_min,avg=(double)sum_arr(array,size)/size;

    for (i=1;i<size-1;i++)
      {
         dev=abs(avg-sum_arr(array,i));
         dev+=abs(avg-sum_arr(array+i,size-i);
         if (dev<cur_min)
         {
              cur_min=dev;
               cur_i=i;
         }
      }
     return cur_i;
}

一个简单的代码是:(未经测试)