BITACM2018第一轮积分赛(三)题解

A. 很会dp

出题人: zhber
AC/TOT: 1/50

题解: 因为 VVV 实在太大了,所以做背包 dpdpdp 肯定不行,这一点在题目中已经很明显的提示了。那么另一种做法只能是暴力枚举每一个物品取还是不取。每件物品可以选取或不取,这样方案数会有 2n2^n2n,是不能接受的。注意到如果 nnn 变成 n2\frac{n}{2}2n,暴力枚举似乎就可以接受了。再考虑到如果 nnn 件物品分成两部分,分别以 2n22^\frac{n}{2}22n 枚举这两部分的所有可能的体积之和,合并的时候不需要在两区间枚举各取什么再加起来,因为这样还是 2n2^n2n,而对于一个区间枚举取的是什么体积(假如取了体积是 sumsumsum ),另一个区间排序之后直接二分小等于 V−sumV-sumVsum 的最大值即可。显然只有这个值对于 sumsumsum 才是有用的,其他值要么加起来超过 VVV,要么加起来不如它优。因此对 nnn 个物品折半之后分别暴力处理所有可能体积,然后枚举前半部分,在后半部分二分即可。这个算法有个专门的名字,叫meet-in-middle有兴趣的同学可以去学习一下“折半搜索”!

参考代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,len;
ll a[40],V,ans;
ll s[200010];
inline ll bsearch(int l,int r,ll x)
{
    ll t=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (s[mid]<=x)t=s[mid],l=mid+1;
        else r=mid-1;
    }
    return t;
}
int main()
{
    scanf("%d%lld",&n,&V);
    for (int i=1;i<=n;i++)scanf("%lld",a+i);
    for (int i=0;i<(1<<(n/2));i++)
    {
        ll sum=0;
        for (int j=1;j<=n/2;j++)
            if (i & (1<<(j-1)) )sum+=a[j];
        if(sum<=V)s[++len]=sum;
    }
    s[++len]=0;
    sort(s+1,s+len+1);
    ans=s[len];
    for (int i=0;i<(1<<(n-n/2));i++)
    {
        ll sum=0;
        for (int j=n/2+1;j<=n;j++)
            if (i & (1<<(j-n/2-1)) )sum+=a[j];
        if(sum<=V)ans=max(ans,sum+bsearch(1,len,V-sum));
    }
    printf("%lld\n",ans);
}
B. 秀外慧中

出题人: zhber
AC/TOT: 0/0

题解: 注意到只要各位有一个零,那么乘积就是零了。因此ttt是零与非零的情况分类讨论。

  • 对于乘积为零的情况,考虑到用总方案数减去不合法方案数,即得到合法的方案数。总方案数是 10k10^k10k,不合法方案就是 kkk 位都不是零,共 9k9^k9k 个。所以答案就是 10k−9k10^k-9^k10k9k。注意答案取模必须是非负的。
  • 对于乘积不为零的情况,注意到填的数字只能是 1...91...91...9,那么ttt分解质因数其实只能有 2、3、5、72、3、5、72357 四个质因数,否则一定无解。如果 t=2a3b5c7dt=2^a3^b5^c7^dt=2
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值