洛谷 P5657 【格雷码】


思路 :此题是由一个序列通过n次转换得来的,因此我们可以把这个序列还原回去,在还原的过程中得到问题的解。

做法 :设第一个格雷码的编号是0,第2个是1……题目中所求的是编号为m(题目中的k)的n位格雷码,通过观察发现这个格雷码序列的前半段的首位都是0,而后半段都是1。所以我们可以通过判断当前m是在前半段还是在后半段来判断出n位编号为m的格雷码的首位是什么,同时我们也可以找出这个格雷码是属于哪个区间了。确定当前区间后,我们可以继续通过以下方法继续确定他所在的区间以及当前这个n位格雷码的编号为m的第i个数是多少,直到把这整个区间缩成一个数为止(用二分来实现)。但是我们需要注意,当确定当前区间后想要找下一位时,其中的顺序不一定是首位为0的在前,首位为1的在后(将上一次确定的区间里所有数的首位都去掉后),但我们依然可以发现一个规律,就是当当前的m在前半段时,下一位所确定的区间一定是0在前,1在后;反之则一定是1在前,0在后。注意如果此时的m在后半段,那么m要减去区间的一半

观察样例 :

2 3

 我们得到2位格雷码有四个 :

00 01 11 10

然后我们找编号为3的格雷码在后半段(编号从0开始的),因此确定第一位为1,输出1,将m - 序列的长度 / 2(因为数据太大,所以代码中是将长度一开始设定为2的n - 1次方了,然后减的直接就是序列的长度,但是意义上和 - 长度 / 2相等),此时的m为1;因为刚刚m在后半段,所以顺序为先1再0,因为此时的m在后半段,所以输出0

代码

 1 #include <bits/stdc++.h>
 2 #define INF 0x3f3f3f3f
 3 #define int unsigned long long//unsigned long long了解一下 
 4 using namespace std;
 5 int n, m, l, f;
 6 signed main()
 7 {
 8     scanf("%llu %llu", &n, &m);
 9     l = pow(2, n - 1);//长度(数据原因,直接先/2了,所以只是2的n - 1次方)
10     for(register int i = 0; l;)//i = 0代表0在前,1在后; 反之则为1。初始时是0在前1在后 
11     {
12         f = (m < l ? 0 : 1);//判断m在前半段还是后半段,前半段为0,反之为1(此时的l已经除过2了) 
13         if(f)//后半段 
14         {
15             printf(i == 0 ? "1" : "0");//输出 
16             i = 1;//改变 
17             m -= l;//别忘了减 
18         }
19         else//前半段 
20         {
21             printf(i == 0 ? "0" : "1");//输出 
22             i = 0;//改变 
23         }
24         l >>= 1;//二分 
25     }
26     return 0;
27 }

猜你喜欢

转载自www.cnblogs.com/qqq1112/p/11964341.html