题目
给一个正整数n(1<=n<=1e50),你只能用包含1的数以及加减两种运算去计算这个数,问最少需要多少个1。
例如,24=11+11+1+1,最少需要6个1;而102=111-11+1+1,最少需要7个1。
思路来源
libreOJ群
题解
一个很重要的性质是,注意到111111*6=1111111-111111*4-1,即用6次看上去不如从高位借下来优,
但是有1*6=11-1*5,所以表明每一个长度的串1都最多用不会超过6次
也就是低位的1不会产生进位,即不会对高位产生影响,
前对后有影响,后对前无影响,这启发我们从高到低dp,
dp[i][j][k]表示考虑前i位时,当前造的前缀这个数和原串前缀i位的数相差为j,当前还有k个前缀1在后续发挥作用时的最小需要的1的个数
其实就是需要存下来两个状态,一个是和真正的答案还偏离多少,
另一个是还有多少个1有后效性,记录下来就没有后效性了
考虑到可能要借位,前面补几个0
代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define dbg1(x) cerr<<#x<<"="<<(x)<<" "
#define dbg2(x) cerr<<#x<<"="<<(x)<<"\n"
const int A=200,B=330,INF=0x3f3f3f3f;
void Min(int &x,int y){x=min(x,y);}
int n,dp[55][A*2+5][B*2+5];
//111111*6=1111111-111111*4-1
//1*6=11-1*5
string s;
int main(){
cin>>s;
s="0000"+s;
memset(dp,INF,sizeof dp);
dp[0][A][B]=0;
n=s.length();
for(int i=0;i<n;++i){
for(int j=-A;j<=A;++j){
for(int k=-6*i;k<=6*i;++k){
if(dp[i][j+A][k+B]==INF)continue;
//dbg1(i),dbg1(j),dbg1(k),dbg2(dp[i][j+A][k+B]);
for(int l=-6;l<=6;++l){
int nk=k+l;
int nj=j*10+nk-(s[i]-'0');
if(nj<-A || nj>A)continue;
Min(dp[i+1][nj+A][nk+B],dp[i][j+A][k+B]+(n-i)*abs(l));
}
}
}
}
int ans=INF;
for(int k=-6*n;k<=6*n;++k){
ans=min(ans,dp[n][A][k+B]);
}
printf("%d\n",ans);
return 0;
}