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);
}