Codeforces gym 100851 B

100 篇文章 0 订阅
20 篇文章 0 订阅

首先我们需要观察一些二又十进制数字具有的性质:

  • 二又十进制数字的十进制表示下只有 0 0 0 1 1 1,且最高位为 1 1 1
  • 二又十进制数字在十进制下的非空后缀去除前导零后一定为 0 0 0或者二又十进制数字。

( 1 ) (1) (1)是显然的,而为了证明 ( 2 ) (2) (2),我们需要先考虑形式化判定一个数字是否为二又十进制的过程。令 n = ( c 1 c 2 ⋯ c k ) 10 n=(c_1 c_2\cdots c_k)_{10} n=(c1c2ck)10,那么 n n n是二又十进制的当且仅当 c 1 = 1 , c i ∈ 0 , 1 , ∀ 1 ≤ i ≤ k c_1=1,c_i\in{0,1},\forall 1\leq i\leq k c1=1,ci0,1,1ik,并且 ∑ i = 1 k c i ⋅ 1 0 k − i ≡ ∑ i = 1 k c i ⋅ 2 k − i ( m o d 2 k ) \sum_{i=1}^{k}c_i\cdot 10^{k-i}\equiv \sum_{i=1}^{k}c_i\cdot 2^{k-i} \pmod {2^k} i=1kci10kii=1kci2ki(mod2k)。事实上,后者等价于 ∑ i = 1 k c i ⋅ ( 1 0 k − 2 k ) ≡ 0 ( m o d 2 k ) \sum_{i=1}^{k}c_i\cdot(10^k-2^k)\equiv 0 \pmod {2^k} i=1kci(10k2k)0(mod2k)。由于 2 k ∣ 1 0 k 2^k\mid 10^k 2k10k 2 k + 1 ∤ 1 0 k 2^{k+1} \nmid 10^k 2k+110k,这意味着十进制下的高位取值不会影响判定一个严格后缀是否是二又十进制数字,我们很容易就可以得到 ( 2 ) (2) (2)了。
事实上,上述观察也给出了一个本题的解题思路。假定我们认为 0 0 0是第 0 0 0小的二又十进制数字,且十进制下位数为 0 0 0,那么对于第 k k k小的二又十进制数字 a k a_k ak(假定十进制下有 w k w_k wk位),可以通过将十进制下某个大于 w k w_k wk的位变为 1 1 1来尝试扩展出一个新的二又十进制数字。若我们将十进制下第 x > k x>k x>k位置为 1 1 1,判定新的数字是否是二又十进制的,只需看它的二进制下的第 k + 1 ∼ x k+1\sim x k+1x位是否与十进制下的对应位相同。进一步的,若我们将 a k a_k ak写为二进制数字,令位置大于 w k w_k wk的第一个 1 1 1在第 p k p_k pk位(不存在设为 + ∞ +\infty +),那么这里合法的 x x x显然满足 w k < x < p k w_k<x<p_k wk<x<pk
我们的算法即为依次扩展出第 1 ∼ n 1\sim n 1n个二又十进制数字,若我们已经得到了第 0 ∼ k − 1 0\sim k-1 0k1个二又十进制数字,则用它们扩展出的最小的未得到过的二又十进制数字即为第 k k k个二又十进制数字。这个过程看起来需要比较两个二又十进制数字并使用优先队列,不过可以发现答案的位数显然有单调性,可以采用链表优化。具体来说,我们将已经求出且还可以继续扩展的二又十进制数字按顺序挂到一个链表里(首个为 0 0 0),我们依次扩展第 1 ∼ n 1\sim n 1n个二又十进制数字,并记录当前最后一个二又十进制数字的十进制位数 d d d,以及上一个扩展出的数字原来在链表中的位置 c u r cur cur(初始为 0 0 0)。假设当前要扩展第 k k k个,我们从 c u r cur cur不断跳到它的后继,从链表中删去途中访问过的第 d d d位为 1 1 1的数,直到 c u r cur cur对应数字的二进制表示中第 d d d位为 0 0 0或是十进制位数已经至少为 d d d。前一种情况我们将 c u r cur cur对应的数字在十进制下第 d d d位改为 1 1 1,即得到第 k k k个二又十进制数字。后一种情况意味着第 k k k个二又十进制数字的十进制位数为 d + 1 d+1 d+1,那么我们可以将 c u r cur cur跳回链表头( 0 0 0的位置),并将 d d d增加 1 1 1,则第 k k k个二又十进制数字即为 1 0 d 10^d 10d。最后将第 k k k个二又十进制数字挂到链表尾完成一轮操作。这样我们就不需要任何对两个二又十进制数字的比较操作即可求出答案。
令第 n n n个二又十进制数字的十进制位数为 L L L L L L显然不通过 n n n,事实上通过实验,可以发现在本题数据规模下 L ≤ 161 L\leq 161 L161。我们发现在上述算法中,我们每次跳到后继时,要么扩展出一个新的二又十进制数字,要么从链表中删去一个数字,因此总的跳跃次数为 O ( n ) \mathcal O(n) O(n)。我们还需要支持给一个已经求出过的二又十进制数字加上一个 1 0 k 10^k 10k,或是查询它在二进制下某一位的值这样的算术操作,执行次数显然同样是 O ( n ) \mathcal O(n) O(n)。这里需要实现高精度运算\cite{2},我们可以预处理出 10 10 10 0 ∼ L 0\sim L 0L次幂,这样每次算术操作均能在 O ( L ) \mathcal O(L) O(L)的时间复杂度下完成,因此总时间复杂度为 O ( n L ) \mathcal O(nL) O(nL)
注意到这里我们取某个位的操作是在二进制下的,因此显然可以采取压位的思想。令计算机字长为 ω \omega ω,我们可以将高精度整数储存在 2 ω 2^{\omega} 2ω进制下,这样单次操作时间复杂度就优化到了 O ( L ω ) \mathcal O(\frac{L}{\omega}) O(ωL),总时间复杂度为 O ( n L ω ) \mathcal O(\frac{nL}{\omega}) O(ωnL)。实际实现的时候可以选择在 2 58 2^{58} 258进制下做高精度运算,这样一个高精度整数可以压成若干个long long储存。

#include <bits/stdc++.h>
#define last last2

using namespace std;

typedef long long ll;

struct Bignum {

ll num[12];

Bignum() {memset(num,0,sizeof(num));}

int query(int d) {
  return (num[d/58]>>(d%58))&1;
}

};

Bignum operator + (Bignum &a,Bignum &b) {
  Bignum c;
  int d=0;
  for(int i=0;i<12;i++) {
  	c.num[i]=a.num[i]+b.num[i]+d;
  	d=(c.num[i]>>58);
  	c.num[i]&=((1LL<<58)-1);
  }
  return c;
}

Bignum operator * (Bignum &a,int b) {
  Bignum c;
  int d=0;
  for(int i=0;i<12;i++) {
  	c.num[i]=a.num[i]*b+d;
  	d=(c.num[i]>>58);
  	c.num[i]&=((1LL<<58)-1);
  }
  return c;
}

Bignum powv[205];

void prepare() {
  powv[0].num[0]=1;
  for(int i=1;i<=200;i++) 
    powv[i]=powv[i-1]*10;
}

int fa[10005],len[10005];
int pre[10005],nxt[10005];
Bignum val[10005];

int main() {
  freopen("binary.in","r",stdin);
  freopen("binary.out","w",stdout);
  prepare();
  int n;
  scanf("%d",&n);
  int last=0,d=0,cur=0;
  len[0]=-1;
  for(int i=1;i<=n;i++) {
  	fa[i]=cur;
  	len[i]=d;
  	val[i]=val[cur]+powv[d];
  	if (val[cur].query(d+1)) {
  		nxt[pre[cur]]=nxt[cur];
  		if (nxt[cur]) pre[nxt[cur]]=pre[cur];
  		else last=pre[cur];
	  }
	if (!val[i].query(d+1)) {
		pre[i]=last;
		nxt[last]=i;
		last=i;
	}
	if (i==n) break;
	if (cur==last||len[nxt[cur]]>=d) {
		cur=0;
		d++;
	}
	else cur=nxt[cur];
  }
  int x=n;
  for(int i=d;i>=0;i--)
    if (len[x]==i) {
    	putchar('1');
    	x=fa[x];
	}
	else putchar('0');
  printf("\n");
  return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值