将数组划分为k个连续分区,使最大分区之和最小

Divide array into k contiguos partitions such that sum of maximum partition is minimum

本文关键字:分区 数组 连续 划分      更新时间:2023-10-16

这里最大和子集是k个给出最大和的子集之一例如:arr = [10,5,3,7], k = 2k个子集中划分arr的可能方法是

  {10,[5,3,7]},{[10,5],[3,7},{[10,5,3],7}

{[10,5],[3,7} is the optimal one.

编辑:它相当于https://www.codechef.com/DI15R080/problems/MINMAXTF

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

这是一个二进制搜索样本空间的例子。

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;
}

复杂度:0 (n log(sum(array))).

但由于对数比线性好得多,这种复杂性是相当好的。

最坏情况的复杂性:O (n日志(INT_MAX * n)) = O (32 n + n log (n)) = O (n log (n))。

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

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

,最后返回结果->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)

阅读本文二进制搜索-> https://www.topcoder.com/community/data-science/data-science-tutorials/binary-search/和解决这个问题-> http://www.spoj.com/problems/AGGRCOW/

您可以在这里找到关于基于动态规划的解决方案的好文章:https://www.geeksforgeeks.org/painters-partition-problem/和它的复杂度是O(k*N²)

为了得到更好的解决方案,您可以使用其他人建议的二分搜索方法。你可以在这里找到一个更详细的解决方案,使用二分搜索:https://www.geeksforgeeks.org/painters-partition-problem-set-2/,它的复杂度是0 (NlogN)

这可以使用动态规划来解决:

首先定义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] -这是主要的解决方案。我们不知道下一个分区有多少元素,所以我们需要遍历所有选项并从中获得最小值。

除法只是一个蛮力问题。你必须关注最小化的函数。所以你要最小化的是与平均值的偏差。

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;
}
一个简单的代码是:(未测试)
// This code works only if you have to find in a contiguous segment
bool isPossible(vector<int>& nums, int mid, int k) {
  int n = nums.size(), Count = 1, Sum = 0;
  for(int i = 0; i < n; i++) {
    if(nums[i] > mid)  return false;
    Sum += nums[i];
    if(Sum > mid) {
      Count++;
      Sum = nums[i]
    }
  }
  return Count <= k;
}
int Func(vector<int>& nums, int k) {
  int n = nums.size();
  int low = 0, high = accumulate(nums.begin(), nums.end(), 0);
  while(low <= high) {
    int mid = (low + high) / 2;
    if(isPossible(nums, mid, k)) {
      ans = mid;
      high = mid - 1;
    }  else  {
      low = mid + 1;
    }
  }
  return ans;
}