「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

建立num1num2数组维护选与不选的结果并sort排序,建立cnt1cnt2变量记录答案的数量。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;
}
posted @ 2023-08-16 15:44  Funny_Cat  阅读(44)  评论(0)    收藏  举报
一键三连