【bzoj1082】【SCOI2005】栅栏
问题描述
农夫约翰打算建立一个栅栏将他的牧场给围起来,因此他需要一些特定规格的木材。于是农夫约翰到木材店购买木材。可是木材店老板说他这里只剩下少部分大规格的木板了。不过约翰可以购买这些木板,然后切割成他所需要的规格。而且约翰有一把神奇的锯子,用它来锯木板,不会产生任何损失,也就是说长度为10的木板可以切成长度为8和2的两个木板。
你的任务:给你约翰所需要的木板的规格,还有木材店老板能够给出的木材的规格,求约翰最多能够得到多少他所需要的木板。
输入格式
第一行为整数m(m<= 50)表示木材店老板可以提供多少块木材给约翰。紧跟着m行为老板提供的每一块木板的长度。
接下来一行(即第m+2行)为整数n(n <= 1000),表示约翰需要多少木材。
接下来n行表示他所需要的每一块木板的长度。木材的规格小于32767。(对于店老板提供的和约翰需要的每块木板,你只能使用一次)。
输出格式
只有一行,为约翰最多能够得到的符合条件的木板的个数。
样例输入
4
30
40
50
25
10
15
16
17
18
19
20
21
25
24
30
样例输出
7
题解
如果约翰能够得到tot个木板,那么对于任意k<=tot,约翰一定能得到k个木板,答案具有单调性,可以二分了。
二分一个答案mid,用dfs检验
然后开始剪枝
显然,对于一块木材,如果不能满足当前木板,也一定不能满足比当前木板长的木板。我们把木材和木板按长度从小到大排序,这样当木材不能满足当前木板时,后面的木板都可以剪掉了。
考虑二分的范围。如果所需木板的总长度比木材的总长度长,显然无论如何不可能满足,直接把最长的木板删掉,直到木板的总长度<=木材的总长度。
对于一块木材,如果不能满足当前木板,也一定不能满足比当前木板长的木板,所以,如果约翰可以得到mid个木板,那么一定是前mid个木板。倒过来从第mid个木板到第1个木板锯,小的木材锯出大的木板,更容易剪枝。
res表示目前锯剩下的不能再锯出需要的木板的木材总长度(即浪费的木材长度),s[]表示木板长度的前缀和,sum表示木材的总长度,如果res+s[mid]>sum,接下来再怎么锯都不能锯出mid个木板,剪枝。
对于res的转移,如果当前木材的剩余长度比所需木板的最小长度还短,显然不可能再锯出木板,res+剩余长度;否则,还有可能锯出木板,res不变。
设las表示上一块锯过的木材,如果当前木板长度和下一个木板一样,下一个从第las块木材开始锯(因为当前木板已经试出las前的木材都锯不出来),否则从第1块木材开始锯。
1 #include <algorithm> 2 #include <cstring> 3 #include <cstdio> 4 int m,n,a[1005],b[1005],c[1005],mid,ans,sum,s[1005]; 5 bool vis[1005]; 6 bool dfs(int res,int las,int t) // 多余长度,提供的上一个木板编号,需要的第t个木板 7 { 8 int i,j,k; 9 if (!t) return 1; 10 if (res+s[mid]>sum) return 0; 11 for (i=las;i<=m;i++) 12 if (b[i]>=c[t]) 13 { 14 b[i]-=c[t]; 15 if (dfs(res+(b[i]<c[1]?b[i]:0),c[t]==c[t-1]?i:1,t-1)) return 1; 16 b[i]+=c[t]; 17 } 18 return 0; 19 } 20 int main() 21 { 22 int i,j,k,l=0,r; 23 scanf("%d",&m); 24 for (i=1;i<=m;i++) 25 scanf("%d",&a[i]), 26 sum+=a[i]; 27 scanf("%d",&n); 28 for (i=1;i<=n;i++) 29 scanf("%d",&c[i]); 30 std::sort(a+1,a+m+1); 31 std::sort(c+1,c+n+1); 32 for (i=1;i<=n;i++) 33 s[i]=s[i-1]+c[i]; 34 while (sum<s[n]) n--; 35 r=n; 36 while (l<=r) 37 { 38 mid=(l+r)>>1; 39 memcpy(b,a,sizeof(b)); 40 if (dfs(0,1,mid)) ans=mid,l=mid+1; 41 else r=mid-1; 42 } 43 printf("%d",ans); 44 return 0; 45 }