USACO section 4.1 Fence Rails(搜索+优化)

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 be cut 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 that can be cut from the supplied boards. Of course, it might not be possible to cut all the possible rails from the given boards.

SAMPLE OUTPUT (file fence8.out)

7

HINTS (use them carefully!)


思路:这是高纬度背包,只要开个dp[50][1023*128]来次背包就完事了,可惜开不出。

 而背包可以用搜索来实现,先将board,tail从小到大排,然后二分其能砍出的段的个数。

重点在于判断是否能砍出。

优化。space记录浪费空间,当space大于,总的borad-tail个数的和就砍不出。

先用小的borad的砍起,tail从大的开始,当tail[x-1]==tail[x] 那么tail[x-1]就不从从最小的开始搜了,可以再tail[x]的基础上开始。


/*
ID:nealgav1
LANG:C++
PROG:fence8
*/
#include<cstdio>
#include<fstream>
#include<cstring>
#include<algorithm>
using namespace std;
const int mm=1029;
ifstream cin("fence8.in");
ofstream cout("fence8.out");
int bo[mm],tail[mm],sum[mm],total,vb[mm],space=0;
int N,R,mid;
bool ok(int x,int z)
{ if(x<1)return 1;
  /// cout<<total<<" total"<<endl;
  if(total-space<sum[mid])///浪费空间过大
    return 0;

  for(int i=z;i<=N;++i)
  {
    if(vb[i]>=tail[x])
    {
      vb[i]-=tail[x];
      if(vb[i]<tail[1])space+=vb[i];///浪费的空间
      if(tail[x-1]==tail[x])///如果两块板一样,则这块板一定在i之后,如果在i之前,早就搜出解了
      {
        if(ok(x-1,i))return 1;
      }
      else if(ok(x-1,1))return 1;
      if(vb[i]<tail[1])space-=vb[i];
      vb[i]+=tail[x];

    }
  }
  return 0;
}
int main()
{
  while(cin>>N)
  { total=0;
    for(int i=1;i<=N;++i)
      {cin>>bo[i];total+=bo[i];}
    cin>>R;
    for(int i=1;i<=R;++i)
      cin>>tail[i];
      sort(bo+1,bo+N+1);sort(tail+1,tail+R+1);

    memset(sum,0,sizeof(sum));
    for(int i=1;i<=R;++i)
      sum[i]=sum[i-1]+tail[i];
    while(total<sum[R])--R;
    int l=1,r=R;int ans=0;
    while(l<=r)
    {
      mid=(l+r)/2;space=0;
      for(int i=1;i<=N;++i)vb[i]=bo[i];
      if(ok(mid,1)){l=mid+1;if(ans<mid)ans=mid;}
      else r=mid-1;
      ///cout<<total<<" ta"<<endl;
     /// cout<<mid<<" mid\n";
    }
    cout<<ans<<"\n";
  }
}

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

USER: Neal Gavin Gavin [nealgav1]
TASK: fence8
LANG: C++

Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.000 secs, 3376 KB]
   Test 2: TEST OK [0.000 secs, 3376 KB]
   Test 3: TEST OK [0.000 secs, 3376 KB]
   Test 4: TEST OK [0.011 secs, 3376 KB]
   Test 5: TEST OK [0.000 secs, 3376 KB]
   Test 6: TEST OK [0.022 secs, 3376 KB]
   Test 7: TEST OK [0.000 secs, 3376 KB]
   Test 8: TEST OK [0.000 secs, 3376 KB]
   Test 9: TEST OK [0.000 secs, 3376 KB]
   Test 10: TEST OK [0.000 secs, 3376 KB]
   Test 11: TEST OK [0.000 secs, 3376 KB]
   Test 12: TEST OK [0.000 secs, 3376 KB]

All tests OK.

YOUR PROGRAM ('fence8') WORKED FIRST TIME! That's fantastic -- and a rare thing. Please accept these special automated congratulations.


posted @ 2013-04-08 21:07  剑不飞  阅读(516)  评论(0编辑  收藏  举报