题意
选一些最少的数,每个数只能用一次,且能把1~n中的每个数用加法表示出来。
题解
搜索+超级剪枝
先解决第一个问,给你p个数,要做到最大的n,那么构造方法是1,2,4,...,2^p-1,那么可以拼出2^p-1的n。根据这个可以求到第一问。
关键在第二问。假设当前已经拼出1~n,那么还能选的数首先前提是没有用过的,还有一定不能大于n+1(想一想)。
所以如果再加上一个数,最大可以去到n+(n+1)也就是2*n+1。
再抽象一点,在满足1~n时,加入一个合法的数k,那么可以拼出的数扩展到1~n+k。可以发现,在合法的前提下,拼出的数的上限就是使用的数的和。
知道这些后,就可以暴力搜索了。
dfs时记录已选的数的个数k,和sum,以及最大的数mx。根据以上特性,这次合法的数的范围是mx+1~sum+1,枚举它们往后转移。
一个超强剪枝,记最少选m个数,当选了m-1个数时,我们就可以计算出结果了。分两大情况:
1、加多一个数也拼不到n,即2*sum+1<n,此时没有方案贡献。
2、再分两种情况:
① 在现在的情况下,选最差的mx+1也可以拼出n,即sum+(mx+1)>=n。那么mx+1~sum+1这些数都可以选,贡献方案(sum+1)-(mx+1)+1=sum-mx+1种;
② 至少要选到x才能满足sum+x>=n,可得x>=n-sum,所以只能选n-sum~sum+1这些数,贡献方案(sum+1)-(n-sum)+1=2*sum-n+2种。
剪枝原理
最后一步直接得出答案。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1010;
int n,m;
int cnt=0;
void dfs(int k,int mx,int sum)
{
if(k==m)//已经选了m-1个
{
if(sum*2+1>=n)
if(sum+mx+1>=n) cnt+=sum-mx+1;
else cnt+=sum*2+2-n;
return ;
}
for(int i=mx+1;i<=sum+1;i++) dfs(k+1,i,sum+i);
}
int main()
{
scanf("%d",&n);
m=1;for(int i=1;i<=n;i<<=1) m++;m--;
dfs(1,0,0);
printf("%d %d\n",m,cnt);
return 0;
}