Analysis: Partitioning the Farm by Jonathan Paulson


The small constraint on N suggests that the solution will be exponential time. The most obvious idea is to check all 2^(2N-2) possible wall positions, but this is too slow.

However, there are only 2^(N-1) possible choices for the vertical walls. And given a particular set of vertical walls, we can use dynamic programming to solve for the optimal placement of horizontal walls.

Specifically, let f(ROW, K) be the minimum possible size of the largest group of cows in rows [ROW...n] using K fences, given that there is a fence just above ROW. For the recurrence, we just pick the next fence: f(ROW, K) = min_{ROW+1 <= NEXT < N} COST[ROW][NEXT] + f(NEXT, K-1) where COST[ROW][NEXT] is defined as the maximum sum between rows ROW and NEXT for the current set of vertical walls.

Assuming we precompute COST, this DP runs in O(N^3) time (there are O(N^2) states and N transitions from each state). It turns out we can also precompute cost in O(N^3) time, so our total runtime will be O(2^N * N^3), which is fast enough.

Here is Richard Peng's code to precompute cost:

for(int i = 0; i < n; ++i) {
        memset(s, 0, sizeof(s));
        for(int i1 = i + 1; i1 <= n; ++i1) {
          cost[i][i1] = 0;
          int s1 = 0;
          for(int j = 0; j < n; ++j) {
            s[j] += v[i1 - 1][j];
            s1 += s[j];
            cost[i][i1] = max(cost[i][i1], s1);
            if((mask >> j) % 2 == 1) {
              s1 = 0;
            }
          }
        }
      }
s is the array of column sums from row i to i1. s1 is the size of the current group of cows. mask is the current set of vertical walls. Every time we increase i1, we do O(N) work to update the column sums and compute the new max-sized group of cows. Here is Richard Peng's C++ solution:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int VERYBIG = 1<<30;
const int MAXN = 20;

int n, k;
int v[MAXN][MAXN];

int ans;

int DP[MAXN][MAXN], cost[MAXN][MAXN], s[MAXN];

int main() {
  freopen("partition.in", "r", stdin);
  freopen("partition.out", "w", stdout);
  scanf("%d%d", &n, &k);
  for(int i = 0; i < n; ++i) {
    for(int j = 0; j < n; ++j) {
      scanf("%d", &v[i][j]);
    }
  }
  ans = VERYBIG;
  for(int mask = 0; mask < (1<<(n - 1)); ++mask) {
    int k1 = 0;
    for(int i = 0; i < n; ++i) {
      k1 += (mask >> i) % 2;
    }
    if(k1 <= k) {
      for(int i = 0; i < n; ++i) {
        memset(s, 0, sizeof(s));
        for(int i1 = i + 1; i1 <= n; ++i1) {
          cost[i][i1] = 0;
          int s1 = 0;
          for(int j = 0; j < n; ++j) {
            s[j] += v[i1 - 1][j];
            s1 += s[j];
            cost[i][i1] = max(cost[i][i1], s1);
            if((mask >> j) % 2 == 1) {
              s1 = 0;
            }
          }
        }
      }
      for(int k2 = 0; k2 <= n; ++k2) {
        for(int i = 0; i <= n; ++i) {
          DP[k2][i] = VERYBIG;
        }
      }
      DP[0][0] = 0;

      for(int k2 = 1; k2 <= n && k2 <= (k - k1 + 1); ++k2) {
        for(int i = 0; i < n; ++i) {
          for(int i1 = i + 1; i1 <= n; ++i1) {
            DP[k2][i1] = min(DP[k2][i1], max(DP[k2 - 1][i], cost[i][i1]));
          }
        }
        ans = min(ans, DP[k2][n]);
      }
    }
  }
  printf("%d\n", ans);
  return 0;
}