VIJOS 1516 N连环
2016-06-27 22:55
120 查看
题意
九连环应该都知道吧!(1)第 1 环可以自由上下
(2)而上/下第 n 环时(n>1),则必须满足:
(a)第 n-1 个环在架上
(b)前 n-2 个环全部在架下
为了让大家多学点知识,特改此题
输入2个N连环的状态 保证环数小于等于100
第一行是初状态
第二行是末状态
输出一个数,需要的步数n(保证n不超过qword)
分析
九连环的数学模型实际上是格雷码我们用一串01串表示N连环的一个状态 记最右边的环为第一个环
九连环包括两个操作
A:将最右边的数位反转 例如:00000->00001
B:将最右边的1左边的数位反转 例如:00101->00111 00100->01100
显然,A和B两种操作任何一种重复做两次,01串等价于没有修改
例如:00000–A->00001–A->00000
因此我们只有让A B操作交替执行
才能让初始串得到其他所有操作序列
这正好就是格雷码的直接排列(以二进制为0值的格雷码为第零项,第一项改变最右边的位元,第二项改变右起第一个为1的位元的左边位元,第三、四项方法同第一、二项,如此反复,即可排列出n个位元的格雷码。)
那么只要将本问题的初末格雷码转换为二进制,两个二进制串的数值差就是答案了
而二进制和格雷码之间具有怎样的关系呢
一个二进制串数值增加1,
如果不进位只会造成最末一位数改变
如果进位将会引起该二进制串lowbit的上一位开始的一系列数位变动
如: 101010->101011 100111->101000
将这两条性质与格雷码的直接排列进行对比
我们可以发现,
格雷码串是二进制串的差分
二进制串是格雷码串的前缀
此处的差分和前缀是建立在模2意义下,证明略去
那么根据这个转换性质就可以很快地把题目解决了
代码
#include<cstdio> #include<cstring> #define fo(i,a,b) for(int i=a;i<=b;i++) #define fe(i,a,b) for(int i=a;i>=b;i--) unsigned long long ans; bool a[200],b[200]; char c[200]; int l; int main(){ scanf("%s",c+1); l=strlen(c+1); fo(i,1,l) if (c[l-i+1]=='0') a[i]=0; else a[i]=1; scanf("%s",c+1); fo(i,1,l) if (c[l-i+1]=='0') b[i]=0; else b[i]=1; fe(i,l-1,1){ a[i]=a[i+1]^a[i];//a[i-1]^a[i]; b[i]=b[i+1]^b[i];//b[i-1]^b[i]; } fe(i,l,1){ if ((a[i]==1)&&(b[i]==0)){ ans+=(unsigned long long)1<<(i-1);// fe(j,i-1,1){ if ((a[j]==1)&&(b[j]==0)) ans+=(unsigned long long)1<<(j-1); if ((a[j]==0)&&(b[j]==1)) ans-=(unsigned long long)1<<(j-1); } printf("%I64u\n",(unsigned long long)ans); return 0; } if ((b[i]==1)&&(a[i]==0)){//得到二进制串的数值差 ans+=(unsigned long long)1<<(i-1); fe(j,i-1,1){ if ((a[j]==1)&&(b[j]==0)) ans-=(unsigned long long)1<<(j-1); if ((a[j]==0)&&(b[j]==1)) ans+=(unsigned long long)1<<(j-1); } printf("%I64u\n",(unsigned long long)ans); return 0; } } printf("0\n");//特殊情况的输出 return 0; }
补充
n进制格雷码串同样也是n进制数串的差分除了格雷码的直接排列外,还有一种镜射排列:
下图可以清晰地展示排列过程(来自wiki)