📜  画家的分区问题

📅  最后修改于: 2021-04-23 05:34:29             🧑  作者: 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个分区。
我们应该怎么做?我们可以在第i个元素与第i + 1个元素之间放置第k-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


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


输出 :

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

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


Java脚本


输出:

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

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