您的位置:首页 > Web前端

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

2018-02-12 21:12 357 查看
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的两个参数是左闭右开的,淦
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  USACO