题目描述
农夫约翰打算建立一个栅栏将他的牧场给围起来,因此他需要一些特定规格的木材。于是农夫约翰到木材店购买木材。可是木材店老板说他这里只剩下少部分大规格的木板了。不过约翰可以购买这些木板,然后切割成他所需要的规格。而且约翰有一把神奇的锯子,用它来锯木板,不会产生任何损失,也就是说长度为10的木板可以切成长度为8和2的两个木板。
你的任务:给你约翰所需要的木板的规格,还有木材店老板能够给出的木材的规格,求约翰最多能够得到多少他所需要的木板。
输入输出格式
输入格式:第一行为整数m(m<= 50)表示木材店老板可以提供多少块木材给约翰。紧跟着m行为老板提供的每一块木板的长度。接下来一行(即第m+2行)为整数n(n <= 1000),表示约翰需要多少木材。接下来n行表示他所需要的每一块木板的长度。木材的规格小于32767。(对于店老板提供的和约翰需要的每块木板,你只能使用一次)。
输出格式:只有一行,为约翰最多能够得到的符合条件的木板的个数。
输入输出样例
输入样例#1:
4 30 40 50 25 10 15 16 17 18 19 20 21 25 24 30
输出样例#1:
7
输入样例#2:
3 20 10 10 9 3 3 3 5 5 7 8 8 9
输出样例#2:
7
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 999999999
#define For(i,a,b) for(i=a;i<=b;++i)
#define rep(i,a,b) for(i=a;i>=b;--i)
#define mm(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
int read(){
int sum=0,flag=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
while(c>='0'&&c<='9')sum=sum*10+c-'0',c=getchar();
return sum*flag;
}
int maxx(int x,int y){
if(x<y)return y;
return x;
}
int minn(int x,int y){
if(x<y)return x;
return y;
}
int abss(int x){
if(x>=0)return x;
return -x;
}
const int maxn = 100010;
int n,m,slen[maxn],len[maxn],ans;
void dfs(int sum,int h){
if(sum+m-h+1<ans)return;
if(h==m+1){
ans=maxx(ans,sum);
return;
}
int i,j;
For(i,1,n){//看当前这块木板被从哪一块切下来
if(slen[i]>=len[h]){
slen[i]-=len[h];
dfs(sum+1,h+1);
slen[i]+=len[h];
}
}
dfs(sum,h+1);//也有可能不考虑当前这块木板
}
int main(){
int i,j;
n=read();
For(i,1,n){
slen[i]=read();
}
sort(slen+1,slen+n+1);
m=read();
For(i,1,m){
len[i]=read();
}
sort(len+1,len+m+1);
dfs(0,1);
printf("%d\n",ans);
return 0;
}
后来我试着写了下dp结果发现有很多细节难以处理,只觉得每一块供给的木板分割所满足的木板数尽量多,并在数量最多的情况下做到尽量装满,于是就有了另一个六十分的程序,从TLE变成了WA,最后总结才发现这个DP原来是贪心:具有后效性,帖一波代码
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 999999999
#define For(i,a,b) for(i=a;i<=b;++i)
#define rep(i,a,b) for(i=a;i>=b;--i)
#define mm(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
int read(){
int sum=0,flag=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
while(c>='0'&&c<='9')sum=sum*10+c-'0',c=getchar();
return sum*flag;
}
int maxx(int x,int y){
if(x<y)return y;
return x;
}
int minn(int x,int y){
if(x<y)return x;
return y;
}
int abss(int x){
if(x>=0)return x;
return -x;
}
const int maxn = 100010;
int n,m,slen[maxn],len[maxn],ans,vis[maxn];
struct node{
int cnt,t[50];
};
int dp[maxn],pre[maxn],pos[maxn];
int main(){
int i,j;
n=read();
For(i,1,n){
slen[i]=read();
}
sort(slen+1,slen+n+1);
m=read();
For(i,1,m){
len[i]=read();
}
sort(len+1,len+m+1);
int last=0,k;
int sum=0;
For(i,1,n){
mm(dp,0);mm(pre,0);mm(pos,0);
For(j,1,m){
if(vis[j])continue;
rep(k,slen[i],len[j]){
if(dp[k]<=dp[k-len[j]]+1)dp[k]=dp[k-len[j]]+1,pre[k]=k-len[j],pos[k]=j;//标记那一块用过
}
}
int tmp=slen[i];
while(tmp!=0){//便利那些用过的,标记,下一块木板就不会再用了
if(pos[tmp])++sum,
vis[pos[tmp]]=1;
tmp=pre[tmp];
}
}
printf("%d\n",sum);
return 0;
}
事已至此,只好再想一些剪枝,再结合网上代码,才有了AC的代码:
一些剪枝:
1、先排好序,这样选取的木板一定是排完序后前x块(想想看,假如最多能满足x块,如果满足的不是前面连续的x块,而是后面的一些能够满足,那么前面这x块也一定能满足,所以可以看出来这实质上是组合的问题:举个例子:提供:1 3 6,需要:2 3 4,这样你就会发现,如果按一个个取,就会发现只能得到2和3,但实际上6会切成2和4,3切成3,三种都会得到。所以不是能取就切的,2应该在6那里取。(这个例子借鉴了http://www.cnblogs.com/2014nhc/p/6198644.html))
2、记录前缀和,记录提供的木板的总长度total,那么若前x数的总和大于total,那么后面的直接不予考虑。
3、最关键的:二分答案,看前mid个数能否可以
4、每一次枚举从需要木板中长度最长的那个开始枚举,看哪块提供的木板可以满足,这样快一点
5、记录浪费的提供的木板的长度,具体方法:每一次用当前使用的那块提供的木板剩余的长度与需要的木板中最小的那个木板的长度作比较,若连最小的那块需要的木板长度都无法满足,则说明当前这块提供的木板已经废掉了,用一个rest记录已经废掉的木板长度总和,若提供木板的总长度-废掉木板的总长度<需要的木板的总长度,就说明无法满足,即可以return 0;
6、若下一个需要木板的只等于当前判定的这个木板的值,就从当前提供木板开始枚举,
这里附上一片别人的题解(代码里的解释很详细,我懒得再写一遍了)https://www.luogu.org/wiki/show?name=%E9%A2%98%E8%A7%A3+P2329中 nowayout的题解
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int ans,w,a[2000]={0},c[2000]={0},b[2000]={0},m,n,tot,mid,lef,righ;
bool check(int x,int last)//x指现在还需多少木板,last用于第二个剪枝,下面再讲。
{
int i;
bool fg;
if (w>tot-c[mid]) return false;//剪枝一:如果多出的废料木板的总量大于理想中提供的木板减去现在枚举需要的木板(即理想中能够多出的木板)
//,那么接下来肯定没法提供所需木料了,所以返回false。w在下面有写。
if (x==0) return true;//
for (i=last;i<=m;++i)//枚举能提供的木板
if (a[i]>=b[x])//如果这块提供的木板能切出所需的木板,那么就试着切!
{
a[i]-=b[x];//切完后剩下的多少木料。
if (a[i]<b[1]) w+=a[i];//如果剩下的长度小于最小的所需木板,那么它肯定不能再被利用,算作废料。 w记录废料的总长度。
if (b[x-1]==b[x]) fg=check(x-1,i);
// 如果下块需要的木板,和现在这块切出的木板一样,那么它提供的木板至少要大于等于目前提供的木板,last即用于记录这个位置,如果两块木板长度一样
//,last就为i,即下次只能从i以后能提供的木板开始枚举。
else fg=check(x-1,1);
//否则就从第一块枚举。
if (a[i]<b[1]) w-=a[i];
a[i]+=b[x];//回溯,避免对下次造成影响。
if (fg==true) return true;//如果剩下的都切的出来,那么就成立啦!
}
return false;
}
int main()
{
cin>>m;
tot=0;
for (int i=1;i<=m;++i) {
cin>>a[i];
tot+=a[i];
}
cin>>n;
c[0]=0;
for (int i=1;i<=n;++i) cin>>b[i];
w=0;
sort(a+1,a+m+1); sort(b+1,b+n+1);
for (int i=1;i<=n;++i) c[i]=c[i-1]+b[i];//为了以后的剪枝,后面会写。
while (c[n]>tot) n--;
lef=0;
righ=n;
ans=0;
while (lef<=righ)//二分枚举是否能切出前m块。
{
w=0;
mid=(lef+righ)/2;
if (check(mid,1)==true)
{
ans=mid;
lef=mid+1;
}
else righ=mid-1;
}
cout<<ans<<endl;
return 0;
}