USACO 4.1 Fence Rails (fence8)

/*
既然要装下尽可能多的物品,那么就应该先选入小的物品。所以,先把物品按照重量递增排序。那么:
1)如果前k物品不能装入背包,那么即使把其中一个物品P换成k+1~R中的一个物品Q,
   由于Q的重量大于P,因此也绝对不可能成功装入背包。
2)如果前k个物品可以装入背包,那么前k-1个物品也一定能装入背包。
3)如果前k个物品不能装入背包,那么前k+1个物品也一定不能装入背包。
这样,一个最值问题就转化成了判定性问题,只要枚举出可行的最大k值就可以了。
从2、3两个结论中还可以得知可行性与k的关系是“单调”的,因此,可以使用二分查找优化枚举(貌似不二分也可以通过所有数据)。
为方便起见,记所有背包的容量和为boardsum,前k个物品重量和为railsum[k]。

1)重量大于最大背包容量的物品可以直接删去,容量小于最小物品重量的背包也可以直接删去。(这不算剪枝,应该是预处理)
2)如果可以把前k个物品全部装入,那么浪费掉的容量waste一定是boardsum-railsum[k]。当一个背包的剩余容量小于物品重量的最小值,
   即不能装入任何物品时,把剩余的容量加到current_waste中,一旦current_waste > waste,就说明这种情况一定不能找到解,应该回溯(比较强大)。
3)如果存在两个物品p1,p2的重量相等,因此可以把他们看作是一样的,所以可以直接固定p2的顺序,把它放在p1后面(非常强大)。
4)如果railsum[k]>boardsum,那么这k个物品一定不能放入背包。
5)改变搜索顺序也可以加快速度,由于放入较大的物品是比较难的,因此dfs的时候可以从大的物品开始。
  (这点很容易被忘记,但有些时候改变顺序的确很有效)

第1个剪枝是本人自己想到的。2,3,4都是从NOCOW和google上搜到的,真的很难想到,尤其是第三个。
refer to Jimmy's Thoughts
*/
/*
Executing...
   Test 1: TEST OK [0.000 secs, 3520 KB]
   Test 2: TEST OK [0.000 secs, 3520 KB]
   Test 3: TEST OK [0.011 secs, 3520 KB]
   Test 4: TEST OK [0.011 secs, 3520 KB]
   Test 5: TEST OK [0.011 secs, 3520 KB]
   Test 6: TEST OK [0.022 secs, 3520 KB]
   Test 7: TEST OK [0.000 secs, 3520 KB]
   Test 8: TEST OK [0.011 secs, 3520 KB]
   Test 9: TEST OK [0.011 secs, 3520 KB]
   Test 10: TEST OK [0.000 secs, 3520 KB]
   Test 11: TEST OK [0.000 secs, 3520 KB]
   Test 12: TEST OK [0.000 secs, 3520 KB]
All tests OK.
Your program ('fence8') produced all correct answers!  This is your
submission #3 for this problem.  Congratulations!
*/
/*
ID: haolink1
PROG: fence8
LANG: C++
*/

//#include <iostream>
#include <fstream>
#include <algorithm>    // std::sort
using namespace std;

const int MAX_RAIL = 1024;

int N,R;
int rail[MAX_RAIL];
int board[50];
int boardsum;
int railsum[MAX_RAIL];
int cut_rail_from[MAX_RAIL];
int max_waste,cur_waste; 
ifstream fin("fence8.in");
ofstream cout("fence8.out");
bool SmallFirst(int a, int b){return a < b;}
bool BigFirst(int a, int b){return a > b;}

void init(){
    fin >> N;
    for(int i = 0; i < N; i++){
        fin >> board[i];
        boardsum += board[i];
    }
    fin >> R;
    for(int i = 0; i < R; i++){
        fin >> rail[i];
    }
    sort(rail,rail+R,SmallFirst);
    sort(board,board+N,BigFirst);
    //ignore impossible boards and rails
    while(rail[R - 1] > board[0] && R > 0)    R --;
    while(N > 0 && board[N - 1] < rail[0])    N --;
    if(R == 0 || N == 0){   //The case there is no solution;
        cout << 0 << endl;
        exit(0);//Note to exit, not return;
    }
    railsum[0] = rail[0];
    for(int i = 1;i < R;i ++){
        railsum[i] = railsum[i - 1] + rail[i];
    }
}

bool DFS_Check(int index){
    if(cur_waste > max_waste) return 0;
    if(index < 0) return 1;
    int start = 0;
    if(rail[index] == rail[index+1]) //Note the case index == 1022; so we allocate 1024 for rail; 
        start = cut_rail_from[index+1];
    for(int i = start; i < N; i++){
        if(board[i] >= rail[index]){
            board[i] -= rail[index];
            cut_rail_from[index] = i;
            if(board[i] < rail[0]) cur_waste += board[i];
            int remain = DFS_Check(index-1);
            //restore;
            if(board[i] < rail[0]) cur_waste -= board[i];
            cut_rail_from[index] = -1;
            board[i] += rail[index];
            // We only need one of the solution to put remain rails;
            // Once we find it, return true right away;
            if(remain) return 1; 
        }
    }
    return 0;
}

void BinarySearch(){
    for(int i = 0; i < R; i++) cut_rail_from[i] = -1;
    int low = 0,hight = R - 1;
    for(; hight >= 0 && boardsum < railsum[hight]; hight--);
    while(low < hight){
        //Note here we plus 1 because when low +1 == hight, we want to check mid == hight; 
        int mid = (low + hight + 1)/2; 
        max_waste = boardsum - railsum[mid];
        if(DFS_Check(mid)) low = mid;
        else hight = mid - 1;
    }
    cout << low + 1 << endl;
}

int main(){
    init();
    BinarySearch();
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值