P5657 格雷码【民间数据】
题解
其实这题水啊
打表找规律
【1】0 1
【2】00 01 11 10
【3】000 001 011 010 110 111 101 100
【4】0000 0001 0011 0010 0110 0111 0101 0100
1100 1101 1111 1110 1010 1011 1001 1000
然后我们发现这题其实可以二分做
1.0 真的以为这题很水
n位的二进制格雷码一共有2n种
目的输出编号为k的二进制格雷码
二分查找,查找区间为[ 0 , 2n-1 ]
然后我们像剥洋葱一样,从外到内一层一层输出,一共输出n层
(1)如果k在区间左边,那么显然当前最外层的数字应该是0,否则就是1
(2)然后我们继续往下找,继续缩小查找区间
大体框架是酱紫
while(l!=r) { mid=(l+r)>>1; if(k<=mid) printf("0"); else printf("1"); }
2.0 不好好分析题意
其实仔细分析过打表的人会发现,这么做,问题hin大,样例1可以水过,样例2,3就完蛋
问题出在这一句:
我们分析,如果上一层你是从上层区间右边转移到下一层的,那么读题目:
显然是要标记一下,也就是本来应该输出1,实际上你是由上一层逆序得到,所以相应的应该改为输出0
也就是二分思路改为:
(1)若k在当前查找区间左边,如果它是由上一个查找区间的左区间转移过来,输出0,如果它是由上一个查找区间的右区间转移过来,输出1
(2)若k在当前查找区间右边,如果它是由上一个查找区间的左区间转移过来,输出1,如果它是由上一个查找区间的右区间转移过来,输出0
注意:
这里我们用flag标记是否从上一层的右区间转移来
大体框架:
while(l!=r) { mid=(l+r)>>1; if(k<=mid){ if(flag) printf("1"); else printf("0"); r=mid,flag=0; } else{ if(flag) printf("0"); else printf("1"); l=mid+1,flag=1; } }
恭喜你!
我还是第一次看见这样的结果。。。
3.0 暂时性迷惑行为
所以问题出在哪里???
和神仙讨论之后呢,发现问题很大啊QAQ
1.要用 unsigned long long ,你看这样就很危险会出现负数(数字超范围)
2.为什么要用while呢(TLE警告)
反正每次都是缩小一半的搜索区间,记录下来这几个数字不就好了
4.0终极版
然后第二天重新整理了下思路,重构代码:
1.由于每次二分查找实际用到的只是区间中点mid,所以我们只把mid移动就好了
注意格雷码编号0~2n-1,所以mid做了减1处理
2.用for循环实现
3.p定位区间长度,也就是每次搜下一个区间时,mid的移动量
4.flag表示是否由上一个区间的右半边转移来
代码
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<string> #include<cstring> #include<queue> using namespace std; typedef unsigned long long ll; inline ll read() { ll ans=0; char last=' ',ch=getchar(); while(ch<'0'||ch>'9') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } ll n,k; ll num[64]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824,2147483648,4294967296,8589934592,17179869184,34359738368,68719476736,137438953472,274877906944,549755813888,1099511627776,2199023255552,4398046511104,8796093022208,17592186044416,35184372088832,70368744177664,140737488355328,281474976710656,562949953421312,1125899906842624,2251799813685248,4503599627370496,9007199254740992,18014398509481984,36028797018963968,72057594037927936,144115188075855872,288230376151711744,576460752303423488,1152921504606846976,2305843009213693952,4611686018427387904,9223372036854775808}; bool flag=0; int main() { n=read();k=read(); ll mid=num[n-1]-1,p=n-1; for(int i=1;i<=n;i++){ p--; if(k<=mid){ if(flag) printf("1"); else printf("0"); flag=0; mid-=num[p]; }else{ if(flag) printf("0"); else printf("1"); flag=1; mid+=num[p]; } } return 0; }