P2320 [HNOI2006]鬼谷子的钱袋

HyperlinkHyperlink

https://www.luogu.com.cn/problem/P2320


DescriptionDescription

求用若干个正整数表示i[1,n],iN\forall i\in[1,n],i\in \mathbb{N}的最小正整数个数以及这些正整数

这些正整数不能重复(除了1)

数据范围:n109n\leq 10^9


SolutionSolution

记得背包的二进制拆分做法吗?用2的正整数次幂是一定能表示出这些数,不过在这道题还不是最优的,这种划分方法严格意义上是有log2n+1log_2n+1个的,而正解的做法是妥妥的log2nlog_2n,所以会有误差

考虑分治,设SxS_x为符合题意的表示xx的正整数集合

对于一个数xx,如果它是偶数,那么我们必然可以把它分成两部分,x/2x/2x/2x/2,也就是说答案是{x/2}Sx/2\{x/2\}\cup S_{x/2}

如果它是奇数,那么我们同样可以把它分成两部分,x/2x/2x/2+1x/2+1
在随便打表找找规律,发现奇数的答案就是{x/2+1}Sx/2\{x/2+1\}\cup S_{x/2}

注意到xx是正整数,所以把两部分的答案合并起来就是{(x+1)/2}Sx/2\{(x+1)/2\}\cup S_{x/2}

显然我们只需要每次向集合中加入(x+1)/2(x+1)/2这个元素,然后递归处理即可

时间复杂度:O(log2n)O(log_2n)


CodeCode

#include<cctype>
#include<cstdio>
#define LL long long
using namespace std;int n,j,a[40];
inline LL read()
{
	char c;LL d=1,f=0;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
signed main()
{
	n=read();
	while(n) a[++j]=(n+1)>>1,n>>=1;
	printf("%d\n",j);
	for(register int i=j;i>0;i--) printf("%d ",a[i]);
}
发布了858 篇原创文章 · 获赞 106 · 访问量 7万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 程序猿惹谁了 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览