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-sumV−sum 的最大值即可。显然只有这个值对于 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^k10k−9k。注意答案取模必须是非负的。
- 对于乘积不为零的情况,注意到填的数字只能是 1...91...91...9,那么ttt分解质因数其实只能有 2、3、5、72、3、5、72、3、5、7 四个质因数,否则一定无解。如果 t=2a3b5c7dt=2^a3^b5^c7^dt=2