USACO 6.3.1 Fence Rails 01多背包问题(BS+IDDFS)

12 篇文章 0 订阅
11 篇文章 0 订阅

http://train.usaco.org/usacoprob2?a=NaWWtjt80VA&S=fence8

题目大意:有N(1-50)块原木,要切成R(1-1023)块长度(1-128)可重复的木板,并不一定所有的木板都能被切出来,求能够交货的最大木板数量


错误想法:我一开始正着想觉得有种网络流的感觉,把每个原木看作一个有限容量的节点,和每个木板结点以无限容量的管道连接,木板结点在和汇点以容量为一的管道连接,然后求最大流。但是发现有两个问题:①木板结点必须被充满才可以被计数 ②多个原木结点不能流入同一个木板结点(不能拼接) 感觉这样一来网络流的特性都不对了,于是我放弃了思考!去看了官方提示


题目模型:

原来是需要反着思考,不是用原木去切木板,而是用木板去填充原木,这样一来就成了01多背包问题,但是没学过啊这种高级的背包,我依稀记得大主教有在数据结构习题课上教过用堆实现的方法,但是我在网上没有查到去群里问也没人告诉我,所以我只好老老实实地去想提示中说的IDDFS+剪枝的方法。

※关于IDDFS(iterative deepening depth-first search)可参考维基百科:感觉上就是带有额外迭代参数的DFS

看了这个之后我就想不明白了,因为这个额外的深度参数我不知道他有什么用,因为我们要找的是最多的木板数量,而且某一深度的DFS结果并不能给后来的DFS提供信息,不管怎么样这个迭代参数都要运行到最大。其实我一开始连这个参数该怎么搞应该代表什么都不明白,知道他代表木板数还是看了别人的题解之后  

于是我放弃了思考


整体思路:

参考链接:https://www.cnblogs.com/albert7xie/p/5733061.html

看了别人的题解我才明白了,我忽视了一个隐藏的性质,那就是最优解k必然可以用最小的k根木板满足,而这是显然的,如此一来,我们只要将木板排序,他就具有了二分性(前k小的木板能否全满足),二分的做法就类似于之前在数据结构课的作业里做过的跳石头一样

这样也同时解决了我对于IDDFS中迭代参数的问题,这个迭代参数代表了当前DFS的搜索深度,他不一定是线性增加的,它可以随便怎么搞其实,他只是反应了当前尝试的解的一个性质,所以这个IDDFS其实可以分解成ID+DFS,ID就是迭代参数可以描述解的优劣性(是我们选择解的条件),DFS是搜索过程,判断当前参数描述的解是否存在,之所以是DFS是因为每一个决策的分支太多,BFS做不了

※当然这里的二分是左开右闭的(小于等于lo的解是可行的,大于hi的解是不可行的),因此在确定mid的时候要加一(至于什么时候要加一,我个人的判断方法是要保证每次迭代之后待搜索范围都必须缩小,因为通过判断在移动边界时两个边界的异动情况可以确定)


剪枝操作:

当然这道题还没有结束,提示里都讲了要剪枝那么裸的DFS当然不可能过,到这里我已经连思考的动力都没有了,直接看了别人的题解(还想了很久才理解,淦)。一共有三种主要的剪枝操作:

①原木由大到小排序,由大到小尝试

②当下一块木板与当前木板大小相同时,从当前尝试到的原木开始尝试

③记录原木无效长度(剩余长度小于最小的木板,属于边角料)总和,当大于应当剩余长度(原木总长减去当前二分条件下木板长度总和)时,当前搜索无解

操作原理:

关于①:emmm不知道为什么,反正肯定不会影响答案的正确性,而且要改的话代码要改比较多我就没实验

关于②:很容易理解(呸,明明自己一开始想都想不明白),因为两块木板长度相同,所以在排序的时候谁在前谁在后其实都无所谓,如果下一块木板还是从头开始尝试的话它的搜索与当前木板之前已经做过的搜索是等价的,而搜索的次序已经表明了这些搜索是无效的。而且我还以为这只是追求0.00的优化,没想到如果没有这个处理竟然会超时


关于③:很明显(呸),在迭代参数确定的情况下所采用的木板的长度是固定的,因此其剩余的原木的长度也是固定的,当无效长度大于剩余长度时,解显然是不存在的(这个剪枝在采用的木板总长相比原木长度较长时比较实用)。当然,不用这个剪枝会超时,但是不像原博主说的是个强力剪枝,因为能苟过的点比上一个还多仅仅一个



概括下来就是:用二分的方法枚举描述解的参数,用DFS判断当前解是否可行,而参数和解的可行性是满足二分性的

代码如下:

/*
ID: frontie1
TASK: fence8
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int N, R;
int wood[55], need[1025];
int tot = 0, space;
int sum[1024];
int mid; //used both in DFS() and BS()

bool DFS(int dep, int pos)
{
    if(dep == 0) return true;
    if(space > tot-sum[mid]) return false; //too much space wasted
    for(int i = pos; i <= N; ++i){
        if(wood[i] >= need[dep]){
            wood[i] -= need[dep];
            if(wood[i] < need[1]) space += wood[i];
            bool flag = false;
            if(need[dep-1] == need[dep]) flag = DFS(dep-1, i);
            else flag = DFS(dep-1, 1);
            //backtrack, mind the order!
            if(wood[i] < need[1]) space -= wood[i];
            wood[i] += need[dep];
            if(flag) return true;
        }
    }
    return false;
}

int BS(int lo, int hi)
{
    while(lo < hi){
        mid = ((lo+hi) >> 1)+1;
        space = 0;  //each time initiate
        if(DFS(mid, 1)) lo = mid;
        else hi = mid-1;
    }
    return lo;
}

bool cmp(int a, int b)
{
    return (a > b);
}

int main()
{
    freopen("fence8.in", "r", stdin);
    freopen("fence8.out", "w", stdout);

    cin >> N;
    for(int i = 1; i <= N; ++i){
        cin >> wood[i];
        tot += wood[i];
    }
    cin >> R;
    for(int i = 1; i <= R; ++i){
        cin >> need[i];
    }
    sort(wood+1, wood+N+1,cmp);
    sort(need+1, need+R+1);

    for(int i = 1; i <= R; ++i){
        sum[i] = sum[i-1] + need[i];
    }

    cout << BS(0, R) << endl;

    return 0;
}


注意:除了上面的思路外,我一开始写回溯的时候还颠倒了次序(导致样例输出了6),这种细节问题还是太不小心了

还有一点:原来sort的两个参数是左闭右开的,淦


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是P4087 [USACO17DEC]Milk Measurement的c++代码: ```c++ #include<bits/stdc++.h> using namespace std; int n,d,i,x,minn=1e9,maxn=-1e9,sum=7;//注意sum要初始化为7,因为一开始有三个人挤奶! map<int,int> mp; struct node{ int day,milk,id;//day表示某一天,milk表示这一天的产奶量,id表示这头牛的编号 }a[100010]; bool cmp(node x,node y){ return x.day<y.day; } int main(){ scanf("%d%d",&n,&d); for(i=1;i<=n;i++){ scanf("%d%d%d",&a[i].day,&a[i].id,&a[i].milk); minn=min(minn,a[i].id);//记录最小的牛的编号 maxn=max(maxn,a[i].id);//记录最大的牛的编号 } sort(a+1,a+n+1,cmp);//排序 for(i=1;i<=n;i++){ int p=a[i].id; mp[p]+=a[i].milk;//记录每头牛产奶总量 if(mp[p]-a[i].milk>=mp[minn]&&mp[p]>=mp[minn]){//如果这头牛的产奶总量减去这一天的产奶量后等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 sum--; } if(mp[p]>=mp[maxn]&&mp[p]-a[i].milk<mp[maxn]){//如果这头牛的产奶总量大于等于最大产奶量且这头牛的产奶总量减去这一天的产奶量小于最大产奶量 sum++; } if(mp[p]-a[i].milk<mp[maxn]&&mp[p]>=mp[maxn]){//如果这头牛的产奶总量减去这一天的产奶量小于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]-mp[p]+a[i].milk>0)sum++; } mp[p]-=a[i].milk;//减去这一天的产奶量 if(i==n||a[i].day!=a[i+1].day){//如果到了新的一天或者到了最后一天 if(mp[maxn]!=mp[a[i].id]&&mp[a[i].id]>=mp[maxn])sum++;//如果这头牛的产奶总量不等于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]==mp[a[i].id]){//如果这头牛的产奶总量等于最大产奶量 if(a[i].id==maxn)sum+=0;//如果这头牛就是最大产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } if(mp[minn]!=mp[a[i].id]&&mp[a[i].id]>=mp[minn])sum++;//如果这头牛的产奶总量不等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 if(mp[minn]==mp[a[i].id]){ if(a[i].id==minn)sum+=0;//如果这头牛就是最小产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } } } printf("%d\n",sum); return 0; } ``` 该题的解题思路是模拟,需要注意细节问题。我们可以首先将输入的数据按天数排序,然后模拟每一天挤奶的情况,并根据题目要求进行计数即可。具体细节请见代码注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值