About Gray码
Gray码:
先来看这么一个逗比问题:
一个月黑风高的夜晚,你成功潜入了银行。银行里的保险柜上有n个开关,
当每个开关都置于正确位置,保险柜会被打开,你的时间非常值钱,请你制定
一个作战计划,确保$2^n-1$次以内的操作能打开保险柜,不然正义的黑喵
警长会把你抓走!
为了逃避黑喵警长的魔爪,我们在这里得使用格雷码。
格雷码的介绍:
https://en.wikipedia.org/wiki/Gray_code
http://baike.baidu.com/item/%E6%A0%BC%E9%9B%B7%E7%A0%81
下面介绍几种比较实用的姿势。
1. 二进制 -> Gray
LL to_gray(LL x) { return x ^= (x>>1);//返回结果为Gray码的十进制表示。 }
2. Gray -> 二进制
LL to_bin(LL x) { LL y = x; while(x>>=1) { y ^= x; } return y; }
3. 构造长度为n的格雷码
构造姿势有很多种,不过直接把0~(1<<n)的二进制数字依次转为
格雷码是一种很方便的搞法。
vector<int> G; for(int i=0;i<(1<<n);i++) { G.push_back(to_gray(i)); }
来切两道格雷码的题
Gym 101170 H
/* 求出两个格雷码对应的二进制数的十进制表示 做差即可。 */ #include <iostream> #include <vector> using namespace std; typedef long long LL; LL n, tmp[62]; char s1[62], s2[62]; LL to_bin(LL x) { LL y = x; while(x>>=1) { y ^= x; } return y; } int main() { scanf("%lld %s %s", &n, s1+1, s2+1); LL ans1 = 0; for(int i=1;i<=n;i++) { ans1 += (LL)(s1[i]-'0')*(1LL<<(n-i));//此处有可能爆int } LL ans2 = 0; for(int i=1;i<=n;i++) { ans2 += (LL)(s2[i]-'0')*(1LL<<(n-i)); } LL res = to_bin(ans2) - to_bin(ans1) - 1; cout << res << endl; }
Gym 101252 J
/* 题目要我们生成组合 && 相邻的两个组合必须很接近由这 点,我们可以想到Gray码。 注意到台上的人数不能超过k。这和格雷码说好的不一样啊! 其实,如果我们把n阶格雷码中,1的个数大于k的元素删除 就会得到符合要求的序列。比较序列中相邻的两个元素,即 可得出操作。 为什么放逐掉那些数字,就能得到合法解呢? 下面粗略地分析了一下这个细思极恐的问题。 —————————————————昏割线—————————————————————————— 先来看格雷码的另一种构造姿势: a_{0},a_{1},.....a_{n-1}表示的格雷码的下 一个元素的构造方法 1. 如果 a_{0}+a_{1}+...+a_{n-1} 为偶数。 对最低位取反。eg: 101000 -> 101001 2. 如果 a_{0}+a_{1}+...+a_{n-1} 为奇数。 对位数最低的1的前一位取反。eg:101001 -> 101011 分析一下n=5,k=4的情况。 11110 舞台上有4个人 11111 咦!有5个人了,多出来了一个人。 11101 第1位 与 第0位 发生了交换 再来看看n=5,k=2的情况。 11000 舞台上有2人 11001 人多啦! 11011 11010 11110 (喵) 11111 (喵) 11101 (喵) 11100 10100 此时,舞台上人数为2。而且是11000的位数最低的1与 其右边发生了交换。注意到(喵喵喵)那三行。对应的正是n=5,k=4 的情况,因此该问题是可以通过第二类数学归纳法得到证明。 */ #include <iostream> #include <algorithm> #include <vector> #include <cstring> #include <cstdio> using namespace std; typedef long long LL; vector<int> vc; int n, k; //求出符合要求的序列。 void to_gray(LL x) { int cnt = 0, ans = x^(x>>1); while(ans) { if(ans&1) cnt ++; ans >>= 1; } if(cnt > k) return; return vc.push_back(x^(x>>1)); } //比较序列中相邻的两个元素,并得出该怎样操作。 void cmp(int x, int y) { int tmp = x^y; int plus = 0, substr = 0; for(int i=0;i<n;i++) { if((1<<i)&tmp) { if((1<<i)&x) { substr = i+1; } if((1<<i)&y) { plus = i+1; } } } if(plus && !substr) { printf("+%d", plus); } if(!plus && substr) { printf("-%d", substr); } if(plus && substr) { if(plus > substr) { printf("++%d", substr); } else { printf("--%d", substr); } } } int main() { freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); cin >> n >> k; for(int i=0;i<(1<<n);i++) { to_gray(i); } for(int i=1;i<vc.size();i++) { cmp(vc[i-1], vc[i]); } printf("-%d", n); }