题解——P2329 [SCOI2005]栅栏
解题分析
看到这个题面,自然会想到一个二分模板题,其思路是对于答案进行二分,回过头判断当前的答案是否可以被满足。那么本题和上一题的区别在于,需求的木板长度也是不一样的,对应的解决措施是把循环实现的判断函数改为搜索。
实现
基本操作
首先进行升序排序以保证木材被最大化的利用,然后进行二分。二分的区间为 \([0,n]\) 意义是能否满足前 \(mid\) 个木块的需要,区间的缩减是:
if(check()) ans=mid,l=mid+1;
else r=mid-1;
这里用 \(ans\) 记录答案,不需要缩小边界时不需要包括 \(mid\)。同时注意每次搜索判断时都要把木材数组初始化,建议开一个 \(tmp\) 数组。
优化
这样的搜索显然是会 \(TLE\) 的,考虑两方面的优化。
关于二分区间
可以发现,如果木材总长度小于需求木板总长度,一定是无法实现的,并且会占用时间,所以可以通过该判断缩小右边界 \(n\)。
while(suma
关于搜索剪枝
先来看初始版本的搜索:
inline bool check(int pos){
if(!pos) return 1;
for(int i=1;i<=m;i++){
if(tmp[i]>=q[pos]){
tmp[i]-=q[pos];
if(check(pos-1) return 1;
tmp[i]+=q[pos];
}
}
return 0;
}
大致思路是:倒序搜索木材正序枚举木料,如果能够满足全部就返回值为真。
先考虑可行性剪枝,和对二分区间的优化相同,如果目前的剩余木材小于木板总长度,那么一定不可行。需要更新变量 \(waste\),注意回溯顺序。
其次是比较玄学的剪枝,如果下一块木板和这一块木板长度相同,那么下次枚举从当前木材开始即可,原因是前面的木材都无法满足当前木板长度。
代码
int n,m;
int a[1005],q[1005],tmp[1005];
int suma,sumq[1005];
int l,r,mid;
inline bool check(int pos,int last,int waste=0){
if(waste>suma-sumq[mid]) return 0;
if(!pos) return 1;
for(int i=last;i<=m;i++){
if(tmp[i]>=q[pos]){
tmp[i]-=q[pos];
if(tmp[i]>1;
for(int i=1;i<=n;i++){
tmp[i]=a[i];
}
if(check(mid,1)){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
printf("%d\n",ans);
return 0;
}