JZOJ[3771] 【NOI2015模拟8.15】小 Z 的烦恼

题目

描述

在这里插入图片描述

题目大意

有从 1 1 1 n n n的数字,每个数字都可以放在一个盒子里(可以不放)。一旦放,满足:
如果它不在第 m m m个盒子,那么它的两倍一定在后面一个盒子里。
如果它不在第 1 1 1个盒子,那么它的一半(整除,如果不能整除即为不合法)一定在前面的一个盒子里。
询问第一个盒子放的最多的数字个数。


思考历程

其实我当时思考的应该是正解,不过有点麻烦。
题目的重点自然在哪两个限制的条件上。首先,显然奇数必须放在第 1 1 1个盒子里
可以互相影响到的其实是 2 k x 2^kx 2kx的形式,其中 x x x为奇数。
不妨将每个 x x x的这个东西想象成一条链。
如果这条链被放入盒子中,第一项必定在第一个盒子里,然后第二项放第二个盒子里……第 m m m项放在第三个盒子里。
当它的项数达不到 m m m的时候,意味着这条链连不到最后一个盒子,由第一条限制得这样子不合法。
因此,必须要从头连到尾
对于一个以 x x x开头的链,设它的长度为 l l l,它可以先从头到尾连一遍,然后重新来再连一遍……所以说,它最多可以连 ⌊ l m ⌋ \lfloor \frac{l}{m} \rfloor ml层。显然 l = ⌊ log ⁡ 2 n x ⌋ + 1 l=\lfloor\log_2\frac{n}{x}\rfloor+1 l=log2xn+1
所以暴力的思路就出来了,枚举奇数 x x x,然后计算它对答案的贡献。
显然这样是很慢的,因为 x x x的取值有很多。
可是 log ⁡ 2 x \log_2x log2x的取值并不是很多啊!
然后就是第二个思路,枚举 y y y(也就是 log ⁡ 2 x \log_2x log2x),这里 x x x的取值范围是 ⌊ n 2 y + 1 ⌋ &lt; x ≤ ⌊ n 2 y ⌋ \lfloor\frac{n}{2^{y+1}}\rfloor&lt;x \leq \lfloor \frac{n}{2^y} \rfloor 2y+1n<x2yn
我们可以将这段区间内的奇数个数计算出来,计入贡献。
这样子显然要快多了。
再看看高精度的做法,如果是用以前的那种求出 1 1 1分别到左右边界的奇数个数,然后相减的方法,那么花费的时间一定很多。
所以呢?我就开始分类讨论,讨论 ⌊ n 2 y ⌋ \lfloor \frac{n}{2^y} \rfloor 2yn ⌊ n 2 y + 1 ⌋ \lfloor\frac{n}{2^{y+1}}\rfloor 2y+1n的奇偶性,还有他们之间奇数个数和 ⌊ n 2 y + 2 ⌋ \lfloor\frac{n}{2^{y+2}}\rfloor 2y+2n之间的关系……
的确可以讨论出来,并且也是可以实现的,但是……很复杂啊……
最后我还是没有吧这种方法打上去。


正解

正解的方法要简单多了。
一条链中的数字都可以表示成 2 k x 2^kx 2kx的形式。
第一项时 k = 0 k=0 k=0,第 m m m项时 k = m − 1 k=m-1 k=m1。所以如果可以放一层,那么就要满足 2 m − 1 x ≤ n 2^{m-1}x\leq n 2m1xn
然后我们再从左到右,类似地继续来。
这时第一项时 k = m k=m k=m,第 m m m项时 k = 2 m − 1 k=2m-1 k=2m1,那么就要满足 2 2 m − 1 x ≤ n 2^{2m-1}x\leq n 22m1xn
因此可以这么做:
1、先将 n n n变成 ⌊ n 2 m − 1 ⌋ \lfloor\frac{n}{2^{m-1}}\rfloor 2m1n
2、答案加上 1 1 1 n n n之间奇数个数,也就是 ⌊ n 2 ⌋ + n m o d &ThinSpace;&ThinSpace; 2 \lfloor\frac{n}{2}\rfloor+n \mod 2 2n+nmod2
3、 n = ⌊ n 2 m ⌋ n=\lfloor\frac{n}{2^m}\rfloor n=2mn,然后回到第2步。
为什么第一次是 m − 1 m-1 m1,第二次是 m m m呢?不解释,手推一下就好。
这样做可以将一些能放多层的链的贡献累加起来,代码实现比较简单,时间也比较优秀。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define BIT 12
#define BITS 1000000000000ll
struct Bigint{
	long long v[2001];
};
Bigint n;
int m;
Bigint ans;
inline void read(Bigint &x){
	static char str[100001];
	scanf("%s",str);
	int len=strlen(str);
	memset(x.v,0,sizeof x.v);
	for (int i=len-1;i>=0;i-=BIT){
		x.v[0]++;
		long long w=1;
		for (int j=0;j<BIT && i-j>=0;++j,w*=10)
			x.v[x.v[0]]+=(str[i-j]-'0')*w;
	}
}
inline void write(Bigint &x){
	printf("%lld",x.v[x.v[0]]);
	for (int i=x.v[0]-1;i>=1;--i)
		printf("%012lld",x.v[i]);
	printf("\n");
}
inline void operator/=(Bigint &x,int y){
	long long mo=0;
	for (int i=x.v[0];i>=1;--i){
		x.v[i]=x.v[i]+mo*BITS;
		mo=x.v[i]%y;
		x.v[i]/=y;
	}
	while (x.v[0] && !x.v[x.v[0]])
		x.v[0]--;
	if (!x.v[0])
		x.v[0]=1;
}
inline void calc(){//计算ans
	ans.v[1]+=n.v[1]&1;
	n/=2;
	ans.v[0]=max(ans.v[0],n.v[0]);
	for (int i=1;i<=ans.v[0];++i){
		ans.v[i]+=n.v[i];
		ans.v[i+1]+=ans.v[i]/BITS;
		ans.v[i]%=BITS;
	}
	if (ans.v[ans.v[0]+1])
		ans.v[0]++;
}
int main(){
	int T;
	scanf("%d",&T);
	while (T--){
		read(n);
		scanf("%d",&m);
		memset(ans.v,0,sizeof ans.v);
		n/=1<<m-1;
		while (!(n.v[0]==1 && n.v[1]==0)){//n不为0时
			calc();
			n/=1<<m-1;//在calc()操作中,n=n/2
		}
		write(ans);
	}
	return 0;
}

总结

当自己想得很复杂的时候,应该考虑一下,这是否是恰当的解法……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值