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

A. 很会dp

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

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

参考代码:

#include
#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

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

  • 对于乘积为零的情况,考虑到用总方案数减去不合法方案数,即得到合法的方案数。总方案数是 1 0 k 10^k 10k,不合法方案就是 k k k 位都不是零,共 9 k 9^k 9k 个。所以答案就是 1 0 k − 9 k 10^k-9^k 10k9k。注意答案取模必须是非负的。
  • 对于乘积不为零的情况,注意到填的数字只能是 1...9 1...9 1...9,那么 t t t分解质因数其实只能有 2 、 3 、 5 、 7 2、3、5、7 2357 四个质因数,否则一定无解。如果 t = 2 a 3 b 5 c 7 d t=2^a3^b5^c7^d t=2a

你可能感兴趣的:(技术活,BITACM积分赛)