对于前面的一半答案,有个显然的结论
取1,2,4,8,16……2n是最优的,当且仅当输入的n大于2(i-1),小于2i次时取得最小的答案。对于第二种,我们有两种解法,DP和爆搜,这题提供爆搜解法。看到题解区有一个爆搜,不过他代码跑起来有点慢,这里还提供一个剪枝,比如现在我们最多可以将k这个数之前的数字全部表示出来,那么下一个我们最大可以取到k+1,那么可以表示的最大数就是2k+1,由此,如果我们还能取m个数,那么2m*(k+1)-1就是我们最终可以取到的最大的可以表示的数字了,如果仍然比n小,我们就可以退出循环。此外,记录答案时,我们可以退一层来表示,具体看代码,61ms还算挺快
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int re(){
char c=getchar();int all=0,pd=1;
for(;c>'9'||c<'0';c=getchar()) if(c=='-') pd=-1;
while(c>='0'&&c<='9') all=all*10+c-'0',c=getchar();return all*pd;
}const int N=1005;int qpow[N],ans,f[N],a[N],n;
void dfs(int x,int last,int al){
if(al*2+1>=n){ans+=min(al-a[a[0]]+1,al*2+2-n);return;}
//比如我们有n=20,可以表达的最大数字为13,前面已经取了1,2,4,7,那么理论不考虑前面数字大小的情况下。
//我们可以取7,8,9,10,11,12,13,14共8种,但是由于7和7重合,所以我们只有7种。
//这个左侧表示的是从前面数字大小的情况来考虑,后面的是从最大能取的情况考虑
if((al+1)*qpow[x]-1<n&&al>=2) return;
for(int i=last;i<=al+1;i++){
a[++a[0]]=i;dfs(x-1,i+1,al+i);a[a[0]--]=0;
}
}
int main(){
n=re();qpow[0]=1;for(int i=1;i<=30;i++) qpow[i]=qpow[i-1]*2;
for(int i=0;i<=31;i++) if(n>=qpow[i]&&n<qpow[i+1])
{printf("%d ",i+1);;dfs(i+1,1,0);printf("%d
",ans);return 0;}
}