[4_1_fence8] Search problem: Optimizations

Fence Rails
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:

Fence Rails
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]);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值