[洛谷 P1647]锁 --- 规律 + 二进制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27121257/article/details/82839838

传送门: 洛谷 P1647锁


题目描述

给出N和K,要求生成从 0 0 2 N 1 2^{N-1} 的一个序列,序列的第一项为 0 0 ,并且该序列满足以下三个条件:

(1) 序列长度为 2 N 2^N ,保证 0 0 2 N 1 2^{N-1} 的每个数都用了且只用了一次。

(2) 序列中任意两相邻的数都是由前一个数在其二进制下,改变了具有相同值的若干个位而形成的,即把其中若干个 0 0 变为 1 1 ,或把其中若干个 1 1 变为 0 0 ,并且只能二选一。

(3) 当存在多个序列满足前两个条件的时候,要保证字典序最小,即由前一个数生成后一个数的时候,要挑值最小的数(当然是满足前两个条件的情况下)。

现问你这个序列前 K K 项中的最大值是多少,输出其二进制形式,注意一定要输出 N N 位,包括前导零。


分析

头痛的题,看起来毫无头绪,打个表找找规律吧(暴力有 60 60 分,可惜当时暴力都没有打)

1 : 0 1
2 : 00 01 11 10
3 : 000 001 011 010 110 100 101 111
4 : 0000 0001 0011 0010 0110 0100 0101 0111 1111 1000 1001 1011 1010 1110 1100 1101
5 : 00000 00001 00011 00010 00110 00100 00101 00111 01111 01000 01001 01011 01010 01110 01100 01101 11101 10000 10001 10011 10010 10110 10100 10101 10111 11111 11000 11001 11011 11010 11110 11100

1 : 0 1
2 : 0 1 3 2
3 : 0 1 3 2 6 4 5 7
4 : 0 1 3 2 6 4 5 7 15 8 9 11 10 14 12 13
5 : 0 1 3 2 6 4 5 7 15 8 9 11 10 14 12 13 29 16 17 19 18 22 20 21 23 31 24 25 27 26 30 28
6 : 0 1 3 2 6 4 5 7 15 8 9 11 10 14 12 13 29 16 17 19 18 22 20 21 23 31 24 25 27 26 30 28 60 32 33 35 34 38 36 37 39 47 40 41 43 42 46 44 45 61 48 49 51 50 54 52 53 55 63 56 57 59 58 62

无论是十进制还是二进制,相信规律还是能找出来的吧。对此,本人先抛砖引玉,仅列出所用到几点(参考的二进制):

  1. 数的位置是确定的
  2. 对于一个 n n 位的二进制,最高位出现 1 1 的位置是 2 n 1 + 1 2^{n - 1} + 1
  3. 忽略掉 n n 位二进制的最高位,发现位于 [ 1 , 2 n 1 ] [1,2^{n - 1}] 的数与位于 [ 2 n 1 + 1 , 2 n ] [2^{n - 1} + 1, 2^n] 的数仅仅是由前者整体右移一位(最后一位补至第一位) 得来
    E g . Eg.
    000 001 011 010 110 100 101 111 000 \quad 001 \quad 011 \quad 010 \quad 110 \quad 100 \quad 101 \quad 111
    忽略掉最高位后明显分为两个相似的部分(没对齐就将就一下吧)
    ( 0 ) 00 ( 0 ) 01 ( 0 ) 11 ( 0 ) 10 \quad \quad \quad(0)00 \quad (0)01 \quad (0)11 \quad (0)10
    ( 1 ) 10 ( 1 ) 00 ( 1 ) 01 ( 1 ) 11 (1)10 \quad (1)00 \quad (1)01 \quad (1)11

对此,可以考虑利用上述性质来解题了(找到规律的你们应该可以忽略掉下面的一大段废话了吧):

  1. 利用 规律 2 2 找到前 k k 位数中,二进制下第一个 1 1 的位置 x x (找最高的),然后去掉那些对应位置为 0 0 的(缩小查找的区间)。
  2. 利用 规律 3 3 递归处理后 x 1 x - 1

代码

#include <cstdio>
#include <cstdlib>

#define IL inline
#define open(s) freopen(s".in", "r", stdin); freopen(s".out", "w", stdout);
#define close fclose(stdin); fclose(stdout);

using namespace std;

typedef long long ll;

IL ll read()
{
	ll sum = 0;
	int k = 1;
	char c = getchar();
	for(;'0' > c || c > '9'; c = getchar())
	if(c == '-') k = 0;
	for(;'0' <= c && c <= '9'; c = getchar())
		sum = sum * 10 + c - '0';
	return k ? sum : -sum;
}

int n;
ll m;
IL ll max_(ll x, ll y) { return x > y ? x : y; }

IL void wri(ll x, int t)
{
	if(t - 1) wri(x >> 1, t - 1);
	putchar(x & 1 ? '1' : '0');
}

IL ll power(int t)
{
	ll sum = 1;
	for(; t; --t) sum <<= 1;
	return sum;
}

//查找的区间[l, r],由于long long范围,位运算算不出来,所以直接处理成2^x
IL ll get(ll l, ll r, ll k)
{
	k >>= 1;
	//边界
	if(r == 1) return 0;
	if(r == 2) return 1;
	ll sum = 0;
	for(;k >= r; k >>= 1);//找最高位的1
	l = max_(l, k | 1);
	
	l -= k + 1;
	r -= k + 1;
	//特殊处理一下左移
	if(!l)
	{
		if(l == r) sum = get(k, k, k); else 
		sum = max_(get(1, r, k), get(k, k, k));
	}else
	{
		sum = get(l, r, k);
	}
	
	return sum + k;//加上当前这一位的1
}

int main()
{
	open("lock")
	
	n = read();  m = read();
	
	//转化为二进制输出
	wri(get(1, m, power(n)), n);
	
	close
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_27121257/article/details/82839838