题目描述
在写代码时,我们经常要用到类似 x × a 这样的语句( a 是常数)。众所周知,计算机进行乘法运算是非常慢的,所以我们需要用一些加法、减法和左移的组合来实现乘一个常数这个操作。具体来讲, 我们要把 x × a 替换成:(x<<a0)op1(x<<a1)op2(x<<a2)...opn(x<<an) 这样的形式,其中opi 是+或者-。
举个例子:x × 15 = (x<<4) - (x<<0)。
在本题中,假设左移(包括左移0)和加法、减法所需要的时间都是一个单位的时间,上述的例子所需要的时间是3。
现在给定常数 a 的二进制形式,求实现 x × a 最少需要多少单位的时间。
dp
题意就是让你用一堆二的次幂的加减凑出a,要求拿了凑的数个数尽量小。
我们考虑设f[i]表示凑出了a的前i位的最小操作数。
如果第i位是0,有f[i]=f[i-1]。
如果第i位是1,有两种操作:
直接加上这位,f[i]=f[i-1]+1。
或者加上2i+1,然后减去一个2j,这样[j,i]全部都是1,多出来的部分减掉。
f[i]=min(f[j-1]+2+sum[i]-sum[j-1])
枚举j则复杂度是平方。
注意不同的两个决策j和k,在i移一单位时,变化量一样,因此最优决策很容易维护。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000000+10;
char s[maxn];
int sum[maxn],f[maxn];
int i,j,k,l,t,n,m,ans,mx;
int main(){
scanf("%s",s+1);
n=strlen(s+1);
reverse(s+1,s+n+1);
fo(i,1,n) sum[i]=sum[i-1]+(s[i]=='0');
mx=-1;
fo(i,1,n){
if (s[i]=='0') f[i]=f[i-1];
else{
f[i]=f[i-1]+1;
if (mx) f[i]=min(f[i],f[mx-1]+2+sum[i]-sum[mx-1]);
}
if (mx==-1||f[mx-1]+2+sum[i+1]-sum[mx-1]>=f[i-1]+2+sum[i+1]-sum[i-1]) mx=i;
}
ans=f[n];
printf("%d\n",ans*2-1);
}