Burch, Kolstad, and Schrijvers
Farmer John is trying to erect a fence around part of his field.He has decided on the shape of the fence and has even already installed the posts, but he's having a problem with the rails. The local lumber store has dropped off boards of varying lengths; Farmer John must create as many of the rails he needs from the supplied boards.
Of course, Farmer John can cut the boards, so a 9 foot board can becut into a 5 foot rail and a 4 foot rail (or three 3 foot rails, etc.). Farmer John has an `ideal saw', so ignore the `kerf' (distance lost during sawing); presume that perfect cuts can be made.
The lengths required for the rails might or might not include duplicates (e.g., a three foot rail and also another three foot rail might both be required). There is no need to manufacture more rails(or more of any kind of rail) than called for the list of required rails.
PROGRAM NAME: fence8
INPUT FORMAT
Line 1: | N (1 <= N <= 50), the number of boards |
Line 2..N+1: | N lines, each containing a single integer that represents the length of one supplied board |
Line N+2: | R (1 <= R <= 1023), the number of rails |
Line N+3..N+R+1: | R lines, each containing a single integer (1 <= ri <= 128) that represents the length of a single required fence rail |
SAMPLE INPUT (file fence8.in)
4
30
40
50
25
10
15
16
17
18
19
20
21
25
24
30
OUTPUT FORMAT
A single integer on a line that is the total number of fence rails thatcan be cut from the supplied boards. Of course, it might not bepossible to cut all the possible rails from the given boards.SAMPLE OUTPUT (file fence8.out)
7
Solution 1: DFS search
passed 3 tests
#include <fstream>
using namespace std;
void dfs(const int n, const int rnum, const int r[], const int depth, const int cnt,
int rem[], int* const p_ans) {
if (depth == rnum) {
if (cnt > (*p_ans))
*p_ans = cnt;
return;
}
dfs(n, rnum, r, depth + 1, cnt, rem, p_ans);
for (int i = 0; i < n; ++i)
if (rem[i] >= r[depth]) {
rem[i] -= r[depth];
dfs(n, rnum, r, depth + 1, cnt + 1, rem, p_ans);
rem[i] += r[depth];
}
}
int main() {
ifstream fin("fence8.in");
int n;
fin >> n;
int s[50], rem;
for (int i = 0; i < n; ++i)
fin >> s[i];
int rnum;
fin >> rnum;
int r[1024];
for (int i = 0; i < rnum; ++i)
fin >> r[i];
fin.close();
int ans = 0;
dfs(n, rnum, r, 0, 0, s, &ans);
ofstream fout("fence8.out");
fout << ans << '\n';
fout.close();
return 0;
}
Many optimization ideas provided here: http://www.nocow.cn/index.php/USACO/fence8 and http://apps.topcoder.com/forums/?module=Thread&threadID=505915&start=0&mc=9#506775
About IDDFS: http://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search
About Knapsack problem: http://en.wikipedia.org/wiki/Knapsack_problem
Another multi-dimensional knapsack problem: USACO 3_4_rockers
Solution 2: IDDFS + Cut longest rails first
passed 4 tests
IDEA: Check the 4th test input file, it is easy to come up with optimization 1, which is to reduce the number of rails by ignoring rails whose lengths are longer than max-length board. However, it still cannot pass the tests. Thus, it is better to transform the exhaustive DFS search into IDDFS and check whether it is able to cut out 1 rail, 2 rails, 3 rails, ... (optimization 2)
In this IDDFS case, it is natural to check only the shortest required rails (optimization 3). Further, for the rails of same sizes, there is no need to try smaller-numbered boards which have been tested by previous rails before (optimization 4).
Sadly, this is still not enough. Hence, optimization 5 should be applied.
#include <algorithm>
#include <fstream>
using namespace std;
const bool check(const int n, const int r[], const int depth, const int idx,
int board[]) {
if (depth < 0)
return true;
// OPTIMIZATION 4: For same-size rails, do not try previous boards (POWERFUL)
for (int i = (r[depth] == r[depth + 1]) ? idx : 0; i < n; ++i)
if (board[i] >= r[depth]) {
board[i] -= r[depth];
const bool cutable = check(n, r, depth - 1, i, board);
board[i] += r[depth];
if (cutable)
return true;
}
return false;
}
int main() {
ifstream fin("fence8.in");
int n;
fin >> n;
int s[50];
for (int i = 0; i < n; ++i)
fin >> s[i];
int rnum;
fin >> rnum;
int r[1024];
for (int i = 0; i < rnum; ++i)
fin >> r[i];
fin.close();
// OPTIMIZATION 1: ignore invalid inputs (NOT USEFUL FOR IDDFS)
int maxs = 0;
for (int i = 0; i < n; ++i)
maxs = (maxs > s[i] ? maxs : s[i]);
sort(r, r + rnum);
while (r[rnum - 1] > maxs)
--rnum;
int ans = 0;
// OPTIMIZATION 2: iterative deepening DFS
// OPTIMIZATION 3: only check shortest required rails
// OPTIMIZATION 5: cut longest rails first (POWERFUL)
while (ans <= rnum && check(n, r, ans - 1, 0, s))
++ans;
--ans;
ofstream fout("fence8.out");
fout << ans << '\n';
fout.close();
return 0;
}
Solution 3: Calculating waste for pruning
passed 7 tests
#include <algorithm>
#include <fstream>
using namespace std;
const bool check(const int n, const int r[], const int depth, const int idx, const int maxwaste,
int board[], int waste) {
if (depth < 0)
return true;
// OPTIMIZATION 3: for same-size rails, do not try previous boards
for (int i = (r[depth] == r[depth + 1]) ? idx : 0; i < n; ++i)
if (board[i] >= r[depth]) {
board[i] -= r[depth];
// OPTIMIZATION 5: total waste should not exceed its maximum
bool cutable = true;
if (board[i] < r[0]) {
waste += board[i];
if (waste > maxwaste)
cutable = false;
}
if (cutable)
cutable = check(n, r, depth - 1, i, maxwaste, board, waste);
if (board[i] < r[0])
waste -= board[i];
board[i] += r[depth];
if (cutable)
return true;
}
return false;
}
int main() {
ifstream fin("fence8.in");
int n;
fin >> n;
int s[50], total = 0;
for (int i = 0; i < n; ++i) {
fin >> s[i];
total += s[i];
}
int rnum;
fin >> rnum;
int r[1024], sumr[1024];
for (int i = 0; i < rnum; ++i)
fin >> r[i];
fin.close();
int ans = 1;
// OPTIMIZATION 1: iterative deepening DFS
// OPTIMIZATION 2: only check shortest required rails
sort(r, r + rnum);
sumr[0] = r[0];
for (int i = 1; i < rnum; ++i)
sumr[i] = sumr[i - 1] + r[i];
// OPTIMIZATION 4: cut longest rails first
while (ans <= rnum && check(n, r, ans - 1, 0, total - sumr[ans - 1], s, 0))
++ans;
--ans;
ofstream fout("fence8.out");
fout << ans << '\n';
fout.close();
return 0;
}
Solution 4: Check for apparently invalid inputs
passed all 12 tests
#include <algorithm>
#include <fstream>
using namespace std;
const bool check(const int n, const int r[], const int depth, const int idx, const int maxwaste,
int board[], int waste) {
if (depth < 0)
return true;
// OPTIMIZATION 3: for same-size rails, do not try previous boards
for (int i = (r[depth] == r[depth + 1]) ? idx : 0; i < n; ++i)
if (board[i] >= r[depth]) {
board[i] -= r[depth];
// OPTIMIZATION 5: total waste should not exceed its maximum
bool cutable = true;
if (board[i] < r[0]) {
waste += board[i];
if (waste > maxwaste)
cutable = false;
}
if (cutable)
cutable = check(n, r, depth - 1, i, maxwaste, board, waste);
if (board[i] < r[0])
waste -= board[i];
board[i] += r[depth];
if (cutable)
return true;
}
return false;
}
int main() {
ifstream fin("fence8.in");
int n;
fin >> n;
int s[50], total = 0;
for (int i = 0; i < n; ++i) {
fin >> s[i];
total += s[i];
}
int rnum;
fin >> rnum;
int r[1024], sumr[1024];
for (int i = 0; i < rnum; ++i)
fin >> r[i];
fin.close();
int ans = 1;
// OPTIMIZATION 1: iterative deepening DFS
// OPTIMIZATION 2: only check shortest required rails
sort(r, r + rnum);
sumr[0] = r[0];
for (int i = 1; i < rnum; ++i)
sumr[i] = sumr[i - 1] + r[i];
// OPTIMIZATION 6: ignore obviously invalid inputs
while(sumr[rnum - 1] > total)
--rnum;
// OPTIMIZATION 4: cut longest rails first
while (ans <= rnum && check(n, r, ans - 1, 0, total - sumr[ans - 1], s, 0))
++ans;
--ans;
ofstream fout("fence8.out");
fout << ans << '\n';
fout.close();
return 0;
}
Results:
Executing...
Test 1: TEST OK [0.000 secs, 3360 KB]
Test 2: TEST OK [0.000 secs, 3360 KB]
Test 3: TEST OK [0.000 secs, 3360 KB]
Test 4: TEST OK [0.011 secs, 3360 KB]
Test 5: TEST OK [0.011 secs, 3360 KB]
Test 6: TEST OK [0.011 secs, 3360 KB]
Test 7: TEST OK [0.000 secs, 3360 KB]
Test 8: TEST OK [0.022 secs, 3360 KB]
Test 9: TEST OK [0.032 secs, 3360 KB]
Test 10: TEST OK [0.000 secs, 3360 KB]
Test 11: TEST OK [0.000 secs, 3360 KB]
Test 12: TEST OK [0.000 secs, 3360 KB]
Solution 5: Binary search for the answer
All tests 0.000 secs
#include <algorithm>
#include <fstream>
using namespace std;
const bool check(const int n, const int r[], const int depth, const int idx, const int maxwaste,
int board[], int waste) {
if (depth < 0)
return true;
// OPTIMIZATION 3: for same-size rails, do not try previous boards
for (int i = (r[depth] == r[depth + 1]) ? idx : 0; i < n; ++i)
if (board[i] >= r[depth]) {
//if (i > 0 && board[i] == board[i - 1])
// continue;
board[i] -= r[depth];
// OPTIMIZATION 5: total waste should not exceed its maximum
bool cutable = true;
if (board[i] < r[0]) {
waste += board[i];
if (waste > maxwaste)
cutable = false;
}
if (cutable)
cutable = check(n, r, depth - 1, i, maxwaste, board, waste);
if (board[i] < r[0])
waste -= board[i];
board[i] += r[depth];
if (cutable)
return true;
}
return false;
}
int main() {
ifstream fin("fence8.in");
int n;
fin >> n;
int s[50], total = 0;
for (int i = 0; i < n; ++i) {
fin >> s[i];
total += s[i];
}
int rnum;
fin >> rnum;
int r[1024], sumr[1024];
for (int i = 0; i < rnum; ++i)
fin >> r[i];
fin.close();
// OPTIMIZATION 1: iterative deepening DFS
// OPTIMIZATION 2: only check shortest required rails
sort(r, r + rnum);
sumr[0] = r[0];
for (int i = 1; i < rnum; ++i)
sumr[i] = sumr[i - 1] + r[i];
// OPTIMIZATION 6: ignore obviously invalid inputs
while(sumr[rnum - 1] > total)
--rnum;
// OPTIMIZATION 7: binary search for answer
int low = 0, high = rnum, mid;
while (low < high) {
mid = (low + high + 1) >> 1;
// OPTIMIZATION 4: cut longest rails first
if (check(n, r, mid - 1, 0, total - sumr[mid - 1], s, 0))
low = mid;
else
high = mid - 1;
}
ofstream fout("fence8.out");
fout << low << '\n';
fout.close();
return 0;
}
Official solutions:
Hal Burch
This is a high dimensionality multiple knapsack problem, so we just have to test the cases. Given that the search space has a high out-degree, we will use depth first search with iterative deepening in order to limit the depth of the tree. However, straight DFSID will be too slow, so some tree-pruning is necessary.
Note that if there is a way to cut k rails, there is a way to cut the k shortest rails, so we will only consider subsets of the rails that contain the k shortest rails. Also, we know the sum or the rails cut cannot exceed the sum of the lengths of the rails, so we can stop our DFS-ID if it finds a way to cut the largest set of shortest rails such that the sum of the lengths of the rails is less than the sum of the board lengths.
Since finding a board from which to cut a longer rail is more difficult than finding a board for a shorter rail, we will perform the search in such that the longest rail is cut first, then the second longest rail, etc.
Also, if two rails are of the same length, then cutting the first from board A and the second from board B is the same as cutting the first from board B and the second from board A, so within sets of rails of the same length, we will ensure that the rails are cut from boards of non-decreasing index.
If there are two boards of the same length, we need to check cutting the rail from only the first.
If there is a board of the same length of the rail, then cutting that rail from that board is optimal.
If, when cutting a board, we get a length of board less than the shortest rail, it is useless, and we can discard it from consideration.This reduces the total amount of board-feet left from which to cut the rest of the rails.
Here is Reid Barton's solution, which might or might not match the above description (sorry).
/*
ID: reid001
PROG: fence8
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXBOARDS 50
#define MAXRAILS 1024
int nboards;
int boards[MAXBOARDS];
int next_board;
int nrails;
int rails[MAXRAILS];
int used[MAXRAILS];
int nused;
int best;
int num_V, num_L, num_R;
int comp_func( const void *a, const void *b );
int rev_comp_func( const void *a, const void *b );
void search( void );
int maximal( int k, int next, int other, int smin, int smax,
int remain[], int origid[], int bound );
inline int max( int a, int b )
{
return (a < b) ? b : a;
}
int main( void )
{
FILE *input = fopen( "fence8.in", "r" );
fscanf( input, "%d", &nboards );
for (int i = 0; i < nboards; i++)
fscanf( input, "%d", &boards[i] );
fscanf( input, "%d", &nrails );
for (int i = 0; i < nrails; i++)
fscanf( input, "%d", &rails[i] );
rails[nrails++] = 1000000;
qsort( boards, nboards, sizeof(int), comp_func );
qsort( rails, nrails, sizeof(int), comp_func );
int ans;
if (boards[nboards-1] >= 1000000)
// the answer might be off by one if we could supply the big rail.
// but then we could supply all the other rails, so no need to search.
ans = nrails - 1;
else
{
next_board = 0;
search();
ans = best;
}
FILE *output = fopen( "fence8.out", "w" );
fprintf( output, "%d\n", ans );
//fprintf( stderr, "%d %d %d %d\n", ans, num_V, num_L, num_R );
return 0;
}
int comp_func( const void *a, const void *b )
{
const int *p = (const int *)a;
const int *q = (const int *)b;
if (*p < *q)
return -1;
else if (*p > *q)
return 1;
else
return 0;
}
int rev_comp_func( const void *a, const void *b )
{
return -comp_func( a, b );
}
void search( void )
{
if (next_board == nboards)
{
if (best < nused)
best = nused;
// fprintf( stderr, "nused = %d best = %d\n", nused, best );
return;
}
int nremain;
int remain[MAXRAILS];
int origid[MAXRAILS];
// find remaining rails,
// as well as max # for this board, all remaining boards
int boardsum = 0;
for (int j = next_board; j < nboards; j++)
boardsum += boards[j];
nremain = 0;
int k = 0, l = 0;
for (int j = 0, sum = 0; j < nrails; j++)
if (used[j] == 0)
{
remain[nremain] = rails[j];
origid[nremain] = j;
nremain++;
sum += rails[j];
if (sum <= boards[next_board])
k++;
if (sum <= boardsum)
l++;
}
int bound;
if ((bound = nused + l) <= best)
return;
// try all maximal m-subsets of remaining boards
for (int m = k; m >= 0; m--)
maximal( m, l-1, nremain-1, 0, boards[next_board],
remain, origid, bound );
}
int maximal( int k, int next, int other, int smin, int smax,
int remain[], int origid[], int bound )
{
if (k == 0)
{
if ((smin <= 0) && (0 <= smax))
{
next_board++;
search();
next_board--;
}
return 0;
}
if (k > next+1)
return 0; // not enough boards left
num_V++;
int low_sum = 0;
for (int j = 0; j < k; j++)
low_sum += remain[j];
if (low_sum > smax)
{
num_L++;
return 0;
}
int hi_sum = 0;
for (int j = 0; j < k; j++)
hi_sum += remain[next-j];
if (hi_sum < smin)
{
num_R++;
return 0;
}
int last = other;
for (int m = next; m >= k-1; m--)
{
if (remain[m] != remain[last] && (low_sum - remain[k-1] + remain[m]) <= smax)
{
int new_min = max( smin - remain[m], smax - remain[last] + 1 );
used[origid[m]] = 1;
nused++;
int x = maximal( k-1, m-1, last, new_min, smax - remain[m],
remain, origid, bound );
used[origid[m]] = 0;
nused--;
if (k == 1)
return 0;
if (bound <= best)
return 0;
}
last = m;
}
return 0;
}
Hassan Eslami submitted a dramatically faster solution:
#include <cstdlib>
#include <fstream>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxboard=50+1;
const int maxrail=1023+1;
// the arrays contains rails and boards lenght's
int rail[maxrail], board[maxboard];
// the array contains how much of the i_th board remain for cutting the rails from
int remain[maxboard];
// number of boards and rails
int nboard, nrail;
// sumlen[i] contains sum of the length of first i rails in the case
// that rails are in the sorted order
int sumlen[maxrail];
// minindex[i] contains the index of smallest board that i_th rail
// can cut from it(rails and boards are in the sorted order)
int minindex[maxrail];
//amount of board that 'can' be waste during one run of DSF
long long maywaste;
//amount of board that waste yet
int waste;
//best solution that we want to find
int best;
ifstream fin("fence8.in");
ofstream fout("fence8.out");
//r is the rail that we want cut from board index1 or index1+1 or ... or nboard-1
void DFS(int r, int index1){
//if r is equal to 0 we must search for the last step of solution
if (r == 0) {
for (int i=index1; i<nboard; ++i)
if (remain[i]>=rail[0]){
//if we can cut best+1 rails, we print the solution
fout << best+1 << endl;
fout.close();
exit(0);
}
return;
}
for (int i=index1; i<nboard; ++i)
//for cutting r_th rail from i_th board this condition must hold
if (remain[i]>=rail[r]) {
int oldwaste=waste;
remain[i]-=rail[r];
//now we check if i_th board being useless, amount of
// wasted board must be less than 'maywaste'
if (remain[i]<rail[0] && waste+remain[i]>maywaste) {
remain[i]+=rail[r];
continue;
}
// if i_th board being useless we must update 'waste'
if (remain[i]<rail[0]) waste+=remain[i];
// now we check if two rails have equal size, then the
// usage of board for them must be in non-decreasing order
if (rail[r-1] == rail[r]) DFS(r-1, i);
else DFS(r-1, minindex[r-1]);
// at last we set the initial state again
remain[i]+=rail[r];
waste=oldwaste;
}
}
int main() {
// reading the input and compute sum of the boards length's,
// also set the initial state of 'remain'
fin >> nboard;
long long sum=0;
for (int i=0; i<nboard; ++i) {
fin >> board[i];
sum+=board[i];
remain[i]=board[i];
}
fin >> nrail;
for (int i=0; i<nrail; ++i)
fin >> rail[i];
fin.close();
// sort the rails and boards
sort(&board[0], &board[nboard]);
sort(&rail[0], &rail[nrail]);
// set maximum number of rails that we want to use, with out loss anything
int temp=0;
sumlen[0]=rail[0];
for (; temp<nrail && sumlen[temp]<=sum; ++temp, sumlen[temp]=sumlen[temp-1]+rail[temp])
;
nrail=temp;
// set minindex array
for (int i=0, j=0; i<nrail; ++i) {
while (j<nboard && rail[i]>board[j]) j++;
minindex[i]=j;
if (j == nboard) {
nrail=i;
break;
}
}
// check out one special case
if (nrail == 0) {
fout << 0 << endl;
fout.close();
return 0;
}
// main part of code that use DFS+ID
for (int i=nrail-1; i>=0; --i){
waste=0;
maywaste=sum-sumlen[i];
best=i;
DFS(i, minindex[i]);
}
}