首先在下参考了评测#29184017
以及http://blog.csdn.net/my_sunshine26/article/details/76652764
我天真的以为这是一个区间dp……没想到竟然是埋藏这么深的一道背包。是在下输了。
来说说题意。有n个数,其中选k个,要让我们输出这k个数之积的最多的0的个数。比如50 4 20里选俩,50*4=200有两个0,4*20=80有1个0,50*20=1000有3个0,所以输出3.
首先要思考,如何能凑出来一个0?要么是0与一个数相乘,要么就是5与2相乘。前者如果是0需要特殊考虑,否则任何的0都是可以分解为5与2的。所以我们可以首先处理出来一个数的5、2的因数个数。
回顾一下0-1背包的dp转移方程:
f[j]=max(f[j],f[j-w[i]]+p[i])
现在我们不妨从另一个角度来思考这个问题:如果用f[i][j]表示以i为开始的j个因数2能达到的最多的因数5的值,那么:
f[i][j]=max(f[i][j],f[i-1][j-pr2[k]]+pr5[i])
还有一点要注意,在循环遍历dp数组的时候,我们应该求的是因数2与因数5的最小值。
实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define MAX 203*60
//如果每个数都有最多2的因子,则共有203*log10^18约为60个2的因子
struct num{
LL n;
int n2,n5;
}w[203];
LL dp[203][MAX],n,k;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i){
cin>>w[i].n;
while(w[i].n%2==0) ++w[i].n2,w[i].n>>=1;//枚举因数2
while(w[i].n%5==0) ++w[i].n5,w[i].n/=5;//枚举因数5
}
for(int i=0;i<n;++i)
for(int j=0;j<MAX;++j)
dp[i][j]=INT_MIN;
dp[0][0]=0;
for(int i=1;i<=n;++i){//当前选择的数i
for(int j=k;j>=1;--j){//选择的第j个数
for(int l=MAX;l>=0;--l){//因数2的个数
if(l-w[i].n2>=0){
dp[j][l]=max(dp[j][l],dp[j-1][l-w[i].n2]+w[i].n5);
}
}
}
}
LL ans=0;
for(LL j=0;j<MAX;++j)//枚举求出要求的结果
ans=max(ans,min(j,dp[k][j]));
cout<<ans;
}
我真是太菜了…感觉NOIP药丸