「TYVJ1340」送礼物 题解
作为惩罚,GY被遣送去帮助某神牛给女生送礼物,
貌似是个好差事
但是在GY看到礼物之后,他就不这么认为了。
某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),
他一次可以搬动重量和在w(w<=2^31-1)以下(包含w)的任意多个物品。
GY希望一次搬掉尽量重的一些物品,
请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。
题目概述
有N件物品,每件物品有自己的重量,每件物品只能选一次。
选取任意件物品使其重量最接近(或等于)W。
输出其重量。
输入输出
W N
G[1]
G[2]
G[N]
思路
18TPS
每件物品价值和重量相等。N件物品,每件有自己的重量且只能选一次,要求其重量最接近W。不妨补上一个条件:每件物品价值和重量相等。
将问题转化为求最大价值,纯纯的01背包模板。
如果你不看数据范围直接打01背包,喜提18分。
我们从题目中可以看出
- 对于100%的数据 N<=46,W<=2^{31}-1
- G[i]<= 2^{31}-1
18分DP01背包代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll w,n,val[47],c[47],f[201];
int main(){
cin>>w>>n;
for(ll i=1;i<=n;i++)
{
cin>>val[i];
c[i]=val[i];
}
for(ll i=1;i<=n;i++)
for(ll j=w;j>=val[i];j--) f[j]=max(f[j],f[j-val[i]]+c[i]);
cout<<f[w];
return 0;
}
感人的数据范围会让你的代码RE,所以舍弃。
18TPS
既然W的范围是2^{31}-1往下,那就看一看N,合意的数据,N<=46!!!直接暴力枚举所有可能方案,直接用大法师!!!
18分DFS代码:
#include <bits/stdc++.h>
using namespace std;
int w,n,ans,val[47],f[47];
void dfs(int k,int sum)
{
if(sum+val[k]>w)
{
ans=max(sum,ans);
return;
}
for(int i=1;i<=n;i++)
{
if(f[i]==0&&sum+val[i]<=w)
{
f[i]=1;
dfs(k+1,sum+val[i]);
f[i]=0;
}
}
}
int main()
{
cin>>w>>n;
for(int i=1;i<=n;i++)
cin>>val[i];
sort(val+1,val+n+1);
dfs(1,0);
cout<<ans;
return 0;
}
然而数据会给你一次巨大的打击。通过尝试发现,W的规模巨大,单纯枚举重量很是愚蠢。暴力舍弃。
So
暴力DFS不行,优先考虑剪枝。考虑折半搜索和迭代加深。
折半搜索就是将一个数列分为两份,分别求出每一份的解,在进行合并
好处是降低时间复杂度。
于是我们可以调用原来的大法师并切成两份,每一份枚举[选与不选]的状态。
Q:这不是状态转移吗?作者为什么不用DP + DFS?
A:因为我不会……
思路明确,代码开始
100TPS
建立num1和num2数组维护选与不选的结果并sort排序,建立cnt1和cnt2变量记录答案的数量。num1, cnt1, num2, cnt2分别用于dfs1和dfs2求答案
void dfs1(int k,int sum)
{
if(sum>w)
return;
if(k>mid)
{
if(sum!=0)//sum不为0是防止一直处于不选的状态
num1[++cnt1]=sum;
return;
}
dfs1(k+1,sum);//不选
dfs1(k+1,sum+v[k]);//选
}
void dfs2(int k,int sum)
{
if(sum>w)
return;
if(k>n)
{
if(sum!=0)//sum不为0是防止一直处于不选的状态
num2[++cnt2]=sum;
return;
}
dfs2(k+1,sum);//不选
dfs2(k+1,sum+v[k]);//选
}
/*
int mid=n>>1;
dfs1(1,mid);
sort(num1+1,num1+cnt1+1);
dfs2(mid+1,n);
sort(num2+1,num2+cnt2+1);
*/
计算出左右区间的答案后要合并,合并的策略是从大到小依次计算,每次以其中一组答案依次求当前答案的最大价值,这里我运用了双指针来求解
int l=1;//l防循环外是为了节省时间
for(int i=cnt2;i>0;i--)
{
while(num2[i]+num1[l]<=w&&l<=cnt1)
l++;
l--;//当循环停下来时num2[i]+num1[l(原本的)]已经大于W了,故l--去其次
ans=max(ans,num2[i]+num1[l]);
}
不经意间,我们的代码零件已经做好了,什么?你说就这?是的,我认为本题的难点在于看出折半搜索和码出大法师。考场上这道题恶心的一点就是普通暴力才18分,数据食不食数据啊!!!
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int v[147];
int num1[10000000],cnt1,num2[100000000],cnt2;
int w,n;
int ans;
int mid;
void dfs1(int k,int sum)
{
if(sum>w)
return;
if(k>mid)
{
if(sum!=0)
num1[++cnt1]=sum;
return;
}
dfs1(k+1,sum);
dfs1(k+1,sum+v[k]);
}
void dfs2(int k,int sum)
{
if(sum>w)
return;
if(k>n)
{
if(sum!=0)
num2[++cnt2]=sum;
return;
}
dfs2(k+1,sum);
dfs2(k+1,sum+v[k]);
}
signed main()
{
cin>>w>>n;
for(int i=1;i<=n;i++)
{
cin>>v[i];
}
mid=n>>1;
dfs1(1,0);
sort(num1+1,num1+cnt1+1);
dfs2(mid+1,0);
sort(num2+1,num2+cnt2+1);
int l=1;
for(int i=cnt2;i>0;i--)
{
while(num2[i]+num1[l]<=w&&l<=cnt1)
l++;
l--;
ans=max(ans,num2[i]+num1[l]);
}
cout<<ans;
return 0;
}
最后本蒟蒻还尝试用贪心/递推解决,哪位大犇帮一下啊!
#include <bits/stdc++.h>
#define int long long
using namespace std;
int w,n;
int a[47],f[47];
int built(int l,int r)
{
if(l==r)
{
if(a[l]>w)
return 0;
return a[l];
}
int mid=l+r>>1;
if(built(l,mid)<=w&&built(mid+1,r)<=w)
{
if(built(l,mid)+built(mid+1,r)<w)
{
return built(l,mid)+built(mid+1,r);
}
else{
return max(built(l,mid),built(mid+1,r));
}
}
return built(l,mid)<=w?built(l,mid)<=w:built(mid+1,r)<=w?built(mid+1,r):0;
}
signed main()
{
cin>>w>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
cout<<built(1,n);
return 0;
}

浙公网安备 33010602011771号