📜  画家的分区问题

📅  最后修改于: 2021-09-17 07:01:33             🧑  作者: Mango

我们必须绘制 n 个长度为 {A1, A2…An} 的板。有 k 个油漆工可用,每个油漆工需要 1 个单位时间来绘制 1 个单位的板。问题是找到获得的最短时间
这项工作是在任何画家只会绘制板的连续部分的约束下完成的,比如板 {2, 3, 4} 或仅板 {1} 或什么都没有,但板 {2, 4, 5}。

例子:

Input : k = 2, A = {10, 10, 10, 10} 
Output : 20.
Here we can divide the boards into 2
equal sized partitions, so each painter 
gets 20 units of board and the total
time taken is 20. 

Input : k = 2, A = {10, 20, 30, 40} 
Output : 60.
Here we can divide first 3 boards for
one painter and the last board for 
second painter.

从上面的例子可以看出,将板子分成 k 个相等的分区的策略显然不适用于所有情况。我们可以观察到,问题可以分解为: 给定一个非负整数数组 A 和一个正整数 k,我们必须将 A 分成 k 个较少的分区,使得分区中元素的最大总和,总体分区被最小化。所以对于上面的第二个例子,可能的划分是:

* 一个分区:所以时间是 100。

* 两个分区:(10) & (20, 30, 40),所以时间是90。同理我们可以放第一个分区
20 (=> 时间 70) 或 30 (=> 时间 60) 之后;所以这意味着最短时间:(100, 90, 70, 60) 是 60。
一个蛮力解决方案是考虑所有可能的连续分区集并计算每种情况下的最大和分区,并返回所有这些情况中的最小值。

1) 最优子结构:
我们可以使用具有以下最优子结构属性的递归来实现朴素的解决方案:
假设我们已经有 k-1 个分区(使用 k-2 个分隔符),我们现在必须放置第 k-1 个分隔符以获得 k 个分区。
我们应该怎么做?我们可以将第 k-1 个分隔符放在第 i 个和第 i+1 个元素之间,其中 i = 1 到 n。请注意,将它放在第一个元素之前与将其放在最后一个元素之后是一样的。
这种安排的总成本可以计算为以下最大值
a) 最后一个分区的代价:sum(Ai..An),其中第k-1个分频器为
在元素 i 之前。
b) 在第 k-1 个分割器左侧已经形成的任何分区的最大成本。
这里 a) 可以使用一个简单的辅助函数来计算总和
数组中两个索引之间的元素。如何找出 b) ?
我们可以观察到 b) 实际上是公平地放置 k-2 个分隔符
可能,所以它是给定问题的一个问题。因此我们可以写出最优
子结构性质为以下递推关系:

画家分区

以下是上述递归方程的实现:

C++
// CPP program for The painter's partition problem
#include 
#include 
using namespace std;
 
// function to calculate sum between two indices
// in array
int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// for n boards and k partitions
int partition(int arr[], int n, int k)
{
    // base cases   
    if (k == 1) // one partition
        return sum(arr, 0, n - 1);   
    if (n == 1)  // one board
        return arr[0];
 
    int best = INT_MAX;
 
    // find minimum of all possible maximum
    // k-1 partitions to the left of arr[i],
    // with i elements, put k-1 th divider
    // between arr[i-1] & arr[i] to get k-th
    // partition
    for (int i = 1; i <= n; i++)
        best = min(best, max(partition(arr, i, k - 1),
                                sum(arr, i, n - 1)));
 
    return best;
}
 
int main()
{
    int arr[] = { 10, 20, 60, 50, 30, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int k = 3;
    cout << partition(arr, n, k) << endl;
 
    return 0;
}


Java
// Java Program for The painter's partition problem
import java.util.*;
import java.io.*;
 
class GFG
{
// function to calculate sum between two indices
// in array
static int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
  
// for n boards and k partitions
static int partition(int arr[], int n, int k)
{
    // base cases   
    if (k == 1) // one partition
        return sum(arr, 0, n - 1);   
    if (n == 1)  // one board
        return arr[0];
  
    int best = Integer.MAX_VALUE;
  
    // find minimum of all possible maximum
    // k-1 partitions to the left of arr[i],
    // with i elements, put k-1 th divider
    // between arr[i-1] & arr[i] to get k-th
    // partition
    for (int i = 1; i <= n; i++)
        best = Math.min(best, Math.max(partition(arr, i, k - 1),
                                sum(arr, i, n - 1)));
  
    return best;
}
 
// Driver code
public static void main(String args[])
{
 int arr[] = { 10, 20, 60, 50, 30, 40 };
  
    // Calculate size of array.
    int n = arr.length;
        int k = 3;
 System.out.println(partition(arr, n, k));
}
}
 
// This code is contributed by Sahil_Bansall


Python3
# Python program for The painter's
# partition problem function to
# calculate sum between two indices
# in array
def sum(arr, frm, to):
    total = 0;
    for i in range(frm, to + 1):
        total += arr[i]
    return total
     
# for n boards and k partitions
def partition(arr, n, k):
     
    # base cases
    if k == 1: # one partition
        return sum(arr, 0, n - 1)
    if n == 1: # one board
        return arr[0]
    best = 100000000
     
    # find minimum of all possible 
    # maximum k-1 partitions to 
    # the left of arr[i], with i
    # elements, put k-1 th divider
    # between arr[i-1] & arr[i] to
    # get k-th partition
    for i in range(1, n + 1):
        best = min(best,
               max(partition(arr, i, k - 1),
                         sum(arr, i, n - 1)))
    return best
     
# Driver Code
arr = [10, 20, 60, 50, 30, 40 ]
n = len(arr)
k = 3
print(partition(arr, n, k))
 
# This code is contributed
# by sahilshelangia


C#
// C# Program for The painter's partition problem
using System;
 
class GFG {
     
// function to calculate sum
// between two indices in array
static int sum(int []arr, int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// for n boards and k partitions
static int partition(int []arr, int n, int k)
{
    // base cases
    if (k == 1) // one partition
        return sum(arr, 0, n - 1);
         
    if (n == 1) // one board
        return arr[0];
 
    int best = int.MaxValue;
 
    // find minimum of all possible maximum
    // k-1 partitions to the left of arr[i],
    // with i elements, put k-1 th divider
    // between arr[i-1] & arr[i] to get k-th
    // partition
    for (int i = 1; i <= n; i++)
        best = Math.Min(best, Math.Max(partition(arr, i, k - 1),
                                           sum(arr, i, n - 1)));
 
    return best;
}
 
// Driver code
public static void Main()
{
    int []arr = {10, 20, 60, 50, 30, 40};
 
    // Calculate size of array.
    int n = arr.Length;
    int k = 3;
     
    // Function calling
    Console.WriteLine(partition(arr, n, k));
}
}
 
// This code is contributed by vt_m


PHP


Javascript


C++
// A DP based CPP program for painter's partition problem
#include 
#include 
using namespace std;
 
// function to calculate sum between two indices
// in array
int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// bottom up tabular dp
int findMax(int arr[], int n, int k)
{
    // initialize table
    int dp[k + 1][n + 1] = { 0 };
 
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1][i] = sum(arr, 0, i - 1);
 
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i][1] = arr[0];
 
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
 
            // track minimum
            int best = INT_MAX;
 
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = min(best, max(dp[i - 1][p],
                              sum(arr, p, j - 1)));      
 
            dp[i][j] = best;
        }
    }
 
    // required
    return dp[k][n];
}
 
// driver function
int main()
{
    int arr[] = { 10, 20, 60, 50, 30, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int k = 3;
    cout << findMax(arr, n, k) << endl;
    return 0;
}


Java
// A DP based Java program for
// painter's partition problem
import java.util.*;
import java.io.*;
 
class GFG
{
// function to calculate sum between two indices
// in array
static int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
  
// bottom up tabular dp
static int findMax(int arr[], int n, int k)
{
    // initialize table
    int dp[][] = new int[k+1][n+1];
  
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1][i] = sum(arr, 0, i - 1);
  
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i][1] = arr[0];
  
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
  
            // track minimum
            int best = Integer.MAX_VALUE;
  
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = Math.min(best, Math.max(dp[i - 1][p],
                              sum(arr, p, j - 1)));      
  
            dp[i][j] = best;
        }
    }
  
    // required
    return dp[k][n];
}
 
// Driver code
public static void main(String args[])
{
 int arr[] = { 10, 20, 60, 50, 30, 40 };
  
    // Calculate size of array.
    int n = arr.length;
        int k = 3;
 System.out.println(findMax(arr, n, k));
}
}
 
// This code is contributed by Sahil_Bansall


Python3
# A DP based Python3 program for
# painter's partition problem
 
# function to calculate sum between
# two indices in list
def sum(arr, start, to):
    total = 0
    for i in range(start, to + 1):
        total += arr[i]
    return total
 
# bottom up tabular dp
def findMax(arr, n, k):
     
    # initialize table
    dp = [[0 for i in range(n + 1)]
             for j in range(k + 1)]
 
    # base cases
    # k=1
    for i in range(1, n + 1):
        dp[1][i] = sum(arr, 0, i - 1)
 
    # n=1
    for i in range(1, k + 1):
        dp[i][1] = arr[0]
 
    # 2 to k partitions
    for i in range(2, k + 1): # 2 to n boards
        for j in range(2, n + 1):
             
            # track minimum
            best = 100000000
             
            # i-1 th separator before position arr[p=1..j]
            for p in range(1, j + 1):
                best = min(best, max(dp[i - 1][p],
                                 sum(arr, p, j - 1)))    
 
            dp[i][j] = best
 
    # required
    return dp[k][n]
 
# Driver Code
arr = [10, 20, 60, 50, 30, 40]
n = len(arr)
k = 3
print(findMax(arr, n, k))
 
# This code is contributed by ashutosh450


C#
// A DP based C# program for
// painter's partition problem
using System;
 
class GFG {
     
// function to calculate sum between
// two indices in array
static int sum(int []arr, int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// bottom up tabular dp
static int findMax(int []arr, int n, int k)
{
    // initialize table
    int [,]dp = new int[k+1,n+1];
 
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1,i] = sum(arr, 0, i - 1);
 
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i,1] = arr[0];
 
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
 
            // track minimum
            int best = int.MaxValue;
 
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = Math.Min(best, Math.Max(dp[i - 1,p],
                                      sum(arr, p, j - 1)));
 
            dp[i,j] = best;
        }
    }
 
    // required
    return dp[k,n];
}
 
// Driver code
public static void Main()
{
    int []arr = {10, 20, 60, 50, 30, 40};
 
    // Calculate size of array.
    int n = arr.Length;
    int k = 3;
    Console.WriteLine(findMax(arr, n, k));
}
}
 
// This code is contributed by vt_m


PHP


Javascript


C++
int sum[n+1] = {0};
 
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
 
 for (int i = 1; i <= n; i++)
   dp[1][i] = sum[i];
 
and using it to calculate the result as:
best = min(best, max(dp[i-1][p], sum[j] - sum[p]));


Java
int sum[] = new int[n+1];
   
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
   
 for (int i = 1; i <= n; i++)
   dp[1][i] = sum[i];
   
and using it to calculate the result as:
best = Math.min(best, Math.max(dp[i-1][p], sum[j] - sum[p]));
 
// This code is contributed by divyesh072019.


C#
int[] sum = new int[n+1];
  
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
  
 for (int i = 1; i <= n; i++)
   dp[1,i] = sum[i];
  
and using it to calculate the result as:
best = Math.Min(best, Math.Max(dp[i-1][p], sum[j] - sum[p]));
 
// This code is contributed by divyeshrabadiya07.


C++
for (int i = k-1; i <= n; i++)
    best = min(best, max( partition(arr, i, k-1),
                            sum(arr, i, n-1)));


Java
for (int i = k-1; i <= n; i++)
      best = Math.min(best, Math.max(partition(arr, i, k-1),
                              sum(arr, i, n-1)));
 
// This code is contributed by pratham76.


C#
for(int i = k - 1; i <= n; i++)
    best = Math.Min(best, Math.Max(partition(arr, i, k - 1),
                                         sum(arr, i, n - 1)));
 
// This code is contributed by rutvik_56


Javascript
for(var i = k - 1; i <= n; i++)
      best = Math.min(best, Math.max(partition(arr, i, k - 1),
                                           sum(arr, i, n - 1)));
                                            
// This code is contributed by Ankita saini


输出 :

90

上述解决方案的时间复杂度是指数级的。

2)重叠子问题:
以下是上式中 T(4, 3) 的部分递归树。

T(4, 3)
     /    /    \ ..         
T(1, 2)  T(2, 2) T(3, 2) 
          /..      /..     
      T(1, 1)    T(1, 1)

我们可以观察到,上述问题中的许多子问题,如 T(1, 1) 正在一次又一次地解决。由于这个问题的这两个性质,我们可以使用动态规划来解决它,无论是通过自顶向下的记忆方法还是自底向上
表格方法。以下是自下而上的表格实现:

C++

// A DP based CPP program for painter's partition problem
#include 
#include 
using namespace std;
 
// function to calculate sum between two indices
// in array
int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// bottom up tabular dp
int findMax(int arr[], int n, int k)
{
    // initialize table
    int dp[k + 1][n + 1] = { 0 };
 
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1][i] = sum(arr, 0, i - 1);
 
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i][1] = arr[0];
 
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
 
            // track minimum
            int best = INT_MAX;
 
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = min(best, max(dp[i - 1][p],
                              sum(arr, p, j - 1)));      
 
            dp[i][j] = best;
        }
    }
 
    // required
    return dp[k][n];
}
 
// driver function
int main()
{
    int arr[] = { 10, 20, 60, 50, 30, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int k = 3;
    cout << findMax(arr, n, k) << endl;
    return 0;
}

Java

// A DP based Java program for
// painter's partition problem
import java.util.*;
import java.io.*;
 
class GFG
{
// function to calculate sum between two indices
// in array
static int sum(int arr[], int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
  
// bottom up tabular dp
static int findMax(int arr[], int n, int k)
{
    // initialize table
    int dp[][] = new int[k+1][n+1];
  
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1][i] = sum(arr, 0, i - 1);
  
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i][1] = arr[0];
  
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
  
            // track minimum
            int best = Integer.MAX_VALUE;
  
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = Math.min(best, Math.max(dp[i - 1][p],
                              sum(arr, p, j - 1)));      
  
            dp[i][j] = best;
        }
    }
  
    // required
    return dp[k][n];
}
 
// Driver code
public static void main(String args[])
{
 int arr[] = { 10, 20, 60, 50, 30, 40 };
  
    // Calculate size of array.
    int n = arr.length;
        int k = 3;
 System.out.println(findMax(arr, n, k));
}
}
 
// This code is contributed by Sahil_Bansall

蟒蛇3

# A DP based Python3 program for
# painter's partition problem
 
# function to calculate sum between
# two indices in list
def sum(arr, start, to):
    total = 0
    for i in range(start, to + 1):
        total += arr[i]
    return total
 
# bottom up tabular dp
def findMax(arr, n, k):
     
    # initialize table
    dp = [[0 for i in range(n + 1)]
             for j in range(k + 1)]
 
    # base cases
    # k=1
    for i in range(1, n + 1):
        dp[1][i] = sum(arr, 0, i - 1)
 
    # n=1
    for i in range(1, k + 1):
        dp[i][1] = arr[0]
 
    # 2 to k partitions
    for i in range(2, k + 1): # 2 to n boards
        for j in range(2, n + 1):
             
            # track minimum
            best = 100000000
             
            # i-1 th separator before position arr[p=1..j]
            for p in range(1, j + 1):
                best = min(best, max(dp[i - 1][p],
                                 sum(arr, p, j - 1)))    
 
            dp[i][j] = best
 
    # required
    return dp[k][n]
 
# Driver Code
arr = [10, 20, 60, 50, 30, 40]
n = len(arr)
k = 3
print(findMax(arr, n, k))
 
# This code is contributed by ashutosh450

C#

// A DP based C# program for
// painter's partition problem
using System;
 
class GFG {
     
// function to calculate sum between
// two indices in array
static int sum(int []arr, int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++)
        total += arr[i];
    return total;
}
 
// bottom up tabular dp
static int findMax(int []arr, int n, int k)
{
    // initialize table
    int [,]dp = new int[k+1,n+1];
 
    // base cases
    // k=1
    for (int i = 1; i <= n; i++)
        dp[1,i] = sum(arr, 0, i - 1);
 
    // n=1
    for (int i = 1; i <= k; i++)
        dp[i,1] = arr[0];
 
    // 2 to k partitions
    for (int i = 2; i <= k; i++) { // 2 to n boards
        for (int j = 2; j <= n; j++) {
 
            // track minimum
            int best = int.MaxValue;
 
            // i-1 th separator before position arr[p=1..j]
            for (int p = 1; p <= j; p++)
                best = Math.Min(best, Math.Max(dp[i - 1,p],
                                      sum(arr, p, j - 1)));
 
            dp[i,j] = best;
        }
    }
 
    // required
    return dp[k,n];
}
 
// Driver code
public static void Main()
{
    int []arr = {10, 20, 60, 50, 30, 40};
 
    // Calculate size of array.
    int n = arr.Length;
    int k = 3;
    Console.WriteLine(findMax(arr, n, k));
}
}
 
// This code is contributed by vt_m

PHP


Javascript


输出:

90

优化:

1)上述程序的时间复杂度O(k*N^3)                       .它可以很容易地降低到O(k*N^2)                       通过预先计算数组中的累积和从而避免重复调用 sum函数:

C++

int sum[n+1] = {0};
 
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
 
 for (int i = 1; i <= n; i++)
   dp[1][i] = sum[i];
 
and using it to calculate the result as:
best = min(best, max(dp[i-1][p], sum[j] - sum[p]));

Java

int sum[] = new int[n+1];
   
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
   
 for (int i = 1; i <= n; i++)
   dp[1][i] = sum[i];
   
and using it to calculate the result as:
best = Math.min(best, Math.max(dp[i-1][p], sum[j] - sum[p]));
 
// This code is contributed by divyesh072019.

C#

int[] sum = new int[n+1];
  
 // sum from 1 to i elements of arr
 for (int i = 1; i <= n; i++)
   sum[i] = sum[i-1] + arr[i-1];
  
 for (int i = 1; i <= n; i++)
   dp[1,i] = sum[i];
  
and using it to calculate the result as:
best = Math.Min(best, Math.Max(dp[i-1][p], sum[j] - sum[p]));
 
// This code is contributed by divyeshrabadiya07.

2) 虽然这里我们考虑将 A 划分为 k 个或更少的分区,但我们可以观察到
当我们将 A 精确地划分为 k 个分区时,总是会出现最佳情况。所以我们可以使用:

C++

for (int i = k-1; i <= n; i++)
    best = min(best, max( partition(arr, i, k-1),
                            sum(arr, i, n-1)));

Java

for (int i = k-1; i <= n; i++)
      best = Math.min(best, Math.max(partition(arr, i, k-1),
                              sum(arr, i, n-1)));
 
// This code is contributed by pratham76.

C#

for(int i = k - 1; i <= n; i++)
    best = Math.Min(best, Math.Max(partition(arr, i, k - 1),
                                         sum(arr, i, n - 1)));
 
// This code is contributed by rutvik_56

Javascript

for(var i = k - 1; i <= n; i++)
      best = Math.min(best, Math.max(partition(arr, i, k - 1),
                                           sum(arr, i, n - 1)));
                                            
// This code is contributed by Ankita saini

并相应地修改其他实现。
锻炼:
你能想出一个使用二分搜索的解决方案吗?有关详细信息,请参阅分配最少页数。
参考:
https://articles.leetcode.com/the-painters-partition-problem/

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程