【牛客小白月赛21】NC201605 Bits 题目链接

题目描述

Nancy喜欢做游戏!

汉诺塔是一个神奇的游戏,神奇在哪里呢?

给出3根柱子,最开始时n个盘子按照大小被置于最左的柱子。

如果盘子数为偶数,则需要将她们全部移动到最右侧的柱子上,否则将她们移动到中间的柱子上。

那么,Nancy该怎样移动呢?请你输出汉诺塔游戏的过程叭!

输入描述:

共一行:一个整数n

输出描述:

共2^n组:每组n+2行,每行3×(2n+1)+4个字符,用.表示空白区域,用|表示柱子区域,用*表示盘子。组与组之间请输出3×(2n+1)+4个-。

具体输出方式请参看样例进行理解。

输入:2

输出:

...................
...|.....|.....|...
..***....|.....|...
.*****...|.....|...
-------------------
...................
...|.....|.....|...
...|.....|.....|...
.*****..***....|...
-------------------
...................
...|.....|.....|...
...|.....|.....|...
...|....***..*****.
-------------------
...................
...|.....|.....|...
...|.....|....***..
...|.....|...*****.

解析

方法一:

一不小心拿了个运行时间最快qwq(8ms)(截至2020-01-28)

在别的题解中看到用二进制来解汉诺塔问题这个视频,又看大多数人都是用递归写的,便想试一试二进制做法。这个视频说的就是汉诺塔与二进制的关系(从视频的第6分钟开始)。

我对这个视频的大致理解就是,在二进制中,比如想从0000加到1111(其实就是f[n]),那就要先加111步(其实就是f[n-1])到0111,再加1步得1000,再加111步到1111,这与汉诺塔的递归策略很相似,想移动n个盘必须先用f[n-1]步移动n-1个到第二个柱,再用1步把第n个移到第三个柱,最后再用f[n-1]步移动n-1个到第三个柱,这显然是最优解,没有任何步数的浪费(其实这就得出了汉诺塔的递推式:f[n]=f[n-1]*2+1)。

所以根据这种二进制与汉诺塔的相似性,我们完全可以通过枚举二进制来模拟汉诺塔的移动,如有n个盘,那我们就要从1枚举到2n-1(即n位二进制1,其实n盘汉诺塔的最少移动次数f[n]就等于2n-1),对于每个数,我们看它最后一个1在第几位,如在第num位,也就是该移动第num

个盘了,就把编号为n的盘向右移动到下一个可行的柱子(柱子3的下一个柱子是柱子1)上。

你想操作二进制第n位的1,必须等第n位后没有1,也就是要把后面的1消除,这与汉诺塔的最优解规律是一致的:如果你想移动第n个盘,必须等这个盘子上没有其他盘,也就是移去上面的盘,所以这种移动的二进制规律肯定是最优解。

掌握了这个思路,接下来就是耐心考虑怎么输出图案了qwq

#include<math.h>
#include<string>
#include<iostream>
using namespace std;
int t[3][12],count[3],n,m,width;
//t[i][j]存储第i号柱的第j层的盘的编号(柱从0开始编号),count是柱上的盘子数,width=2*n+1,即最大盘的宽度
int order[6]= {0,1,2,0,1,2},number[11];
//order是塔的顺序,由于要向右移动到下一个可行的柱子上,柱子编号超过2要回到编号0,number[i]是编号为i的盘所在的柱子
string _begin,_str,str;
//_begin是每组图形的开头部分,_str是“...|.....|.....|...”,str用于输出每行的实际情况
inline void print() {
    for (int floor=n,pos,len;floor;floor--){//从第n层向下依次输出
        str=_str;//初始化为“...|.....|.....|...”
        for (int i=0;i<3;i++){//画上每个柱此层的盘子
            if (count[i]>=floor){//如果有盘子
                len=2*t[i][floor]+1;//盘子宽
                pos=width*i+i+1+(width-len)/2;//画盘子的起始位置,width最大盘的宽度,即柱子间的距离,注意柱子间还会多空出一位
                for (int j=pos+1;j<=pos+len;j++)
                    str[j-1]='*';//画上盘子,由于是字符串,j减1
            }
        }
        cout<<str<<"\n";
    }
}
int main() {
    ios::sync_with_stdio(false);
 
    cin>>n,m=6*n+7;//m是行的长度
 
    for (int i=1; i<=n; i++) t[0][i]=n-i+1;
    count[0]=n;//把n个盘放在第0个柱上 for (int i=0; i<m; i++) _begin+='.',_str+='.';
width=2*n+1;
_str[width/2+1]=_str[width+2+width/2]=_str[width*2+3+width/2]='|';//画上中间的“|”
_begin+="\n"+_str+"\n"; cout<<_begin;
//这里的begin是“................... ...|.....|.....|...”两行,第一行没有“-------------------”
print();//输出第一组图形 string tmp;
for (int i=0; i<m; i++) tmp+='-';
_begin=tmp+"\n"+_begin;
//这里的begin是“------------------- ................... ...|.....|.....|...”三行 for (register int i=1,lim=(1<<n); i<lim; i++) {//从1枚举到2^n-1
int num=log2(i&-i)+1,_number=number[num];//i&-i是最后一位1加上后面的0所表示的数,用log2求出它的二进制位数,num即是要移动的盘子编号,_number是此盘所在柱子
cout<<_begin;
for (int j=1,k; j<3; j++) {//寻找移动到哪一个柱子上
k=order[_number+j];//k是柱子的编号
if (t[k][count[k]]>num || !count[k]) {//如果柱上最后一个盘大于num或柱上没有盘,则可行
number[num]=k,t[k][++count[k]]=num;
count[_number]--;//移动盘子,更新信息
break;
}
}
print();
}
}

此外还有个小细节,就是用这种二进制规律做出来的结果正好符合题目要求:“如果盘子数为偶数,则需要将它们全部移动到最右侧的柱子上,否则将它们移动到中间的柱子上。”可是如果题目要求变换一下(比如把“偶数”换成“奇数”),对于递归做法很好改,但这个做法是不是还行呢?

然而,强大的二进制规律告诉我们:照样可以,只要把第50行换为“for (int j=2,k; j>0; j--)”,即从左开始找即可。

方法二:

由于菜鸡我没怎么写过汉诺塔递归,想试试大家喜闻乐见的递归写法qwq

一不小心又拿了个运行时间最快qwq(8ms)(截至2020-01-28)(逃

只要稍微改动一下即可,关于字符串输出和print()的注释请参照方法一

#include<string>
#include<iostream>
using namespace std;
int t[3][12],count[3],n,m,width;
string _begin,_str,str;
inline void print() {
for (int floor=n,pos,len;floor;floor--){
str=_str;
for (int i=0;i<3;i++){
if (count[i]>=floor){
len=2*t[i][floor]+1;
pos=width*i+i+1+(width-len)/2;
for (int j=pos+1;j<=pos+len;j++)
str[j-1]='*';
}
}
cout<<str<<"\n";
}
} inline void move(int a,int b){//从a柱移动一个到b柱
t[b][++count[b]]=t[a][count[a]--];
cout<<_begin;
print();
} void hanoi(int a,int b,int x){//从a柱移动x个到b柱
if (x==1) {
move(a,b);
return;
}
hanoi(a,3-a-b,x-1);//3-a-b是中转柱,注意柱子编号从0开始
move(a,b);
hanoi(3-a-b,b,x-1);
} int main() {
ios::sync_with_stdio(false);
cin.tie(0); cin>>n,m=6*n+7; for (int i=1; i<=n; i++) t[0][i]=n-i+1;
count[0]=n; for (int i=0; i<m; i++) _begin+='.',_str+='.';
width=2*n+1;
_str[width/2+1]=_str[width+2+width/2]=_str[width*2+3+width/2]='|';
_begin+="\n"+_str+"\n"; cout<<_begin;
print();
string tmp;
for (int i=0; i<m; i++) tmp+='-';
_begin=tmp+"\n"+_begin; n&1 ? hanoi(0,1,n):hanoi(0,2,n);//如果盘子数为偶数,则需要将它们全部移动到最右侧的柱子上,否则将它们移动到中间的柱子上。
}

如果各位大佬有更好的理解或想法,欢迎提出改进和建议qwq!

【牛客小白月赛21】NC201605 Bits的更多相关文章

  1. 【牛客小白月赛21】NC201604 Audio

    [牛客小白月赛21]NC201604 Audio 题目链接 题目大意: 给出三点 ,求到三点距离相等的点 的坐标. 解析 考点:计算几何基础. 初中蒟蒻表示不会什么法向量.高斯消元..qwq 方法一: ...

  2. 树的最长链-POJ 1985 树的直径(最长链)+牛客小白月赛6-桃花

    求树直径的方法在此转载一下大佬们的分析: 可以随便选择一个点开始进行bfs或者dfs,从而找到离该点最远的那个点(可以证明,离树上任意一点最远的点一定是树的某条直径的两端点之一:树的直径:树上的最长简 ...

  3. 牛客网 牛客小白月赛5 I.区间 (interval)-线段树 or 差分数组?

    牛客小白月赛5 I.区间 (interval) 休闲的时候写的,但是写的心情有点挫,都是完全版线段树,我的一个队友直接就水过去了,为啥我的就超内存呢??? 试了一晚上,找出来了,多初始化了add标记数 ...

  4. 牛客小白月赛8 - E - 诡异数字 数位DP

    牛客小白月赛8 - E - 诡异数字 题意: 求区间中,满足限制条件的数字的个数. 限制条件就是某些数字不能连续出现几次. 思路: 比较裸的数位DP, DP数组开一个dp[len][x][cnt] 表 ...

  5. 牛客小白月赛18 Forsaken给学生分组

    牛客小白月赛18 Forsaken给学生分组 Forsaken给学生分组 链接:https://ac.nowcoder.com/acm/contest/1221/C来源:牛客网 ​ Forsaken有 ...

  6. 牛客小白月赛18 Forsaken喜欢数论

    牛客小白月赛18 Forsaken喜欢数论 题目传送门直接点标题 ​ Forsaken有一个有趣的数论函数.对于任意一个数xxx,f(x)f(x)f(x)会返回xxx的最小质因子.如果这个数没有最小质 ...

  7. 牛客小白月赛19 E 「火」烈火燎原 (思维,树)

    牛客小白月赛19 E 「火」烈火燎原 (思维,树) 链接:https://ac.nowcoder.com/acm/contest/2272/E来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空 ...

  8. 牛客小白月赛16 小石的妹子 二分 or 线段树

    牛客小白月赛16 这个题目我AC之后看了一下别人的题解,基本上都是线段树,不过二分也可以. 这个题目很自然就肯定要对其中一个进行排序,排完序之后再处理另外一边,另一边记得离散化. 怎么处理呢,你仔细想 ...

  9. 牛客小白月赛13 小A的柱状图(单调栈)

    链接:https://ac.nowcoder.com/acm/contest/549/H来源:牛客网 题目描述 柱状图是有一些宽度相等的矩形下端对齐以后横向排列的图形,但是小A的柱状图却不是一个规范的 ...

随机推荐

  1. NoSQLBooster如何MongoDB的部分文档从一个集合拷贝到另外一个集合中

    假设MongoDB数据库中存有collection_A和collection_B两个集合,如下图所示: (一)先从集合collection_A中拷贝选择的文档 打开collection_A,看到目前有 ...

  2. 吴裕雄--天生自然HADOOP操作实验学习笔记:mapreduce和yarn命令

    实验目的 了解集群运行的原理 学习mapred和yarn脚本原理 学习使用Hadoop命令提交mapreduce程序 学习对mapred.yarn脚本进行基本操作 实验原理 1.hadoop的shel ...

  3. 【终端命令】SSH服务,远程登录

    一.SSH协议 在Linux中SSH是非常常用的工具,通过SSH客户端我们可以连接到运行了SSH服务器的远程机器上. SSH客户端是一种 使用"Secure Shell (SSH)" ...

  4. 一键安装最新内核并开启 BBR 脚本

    最近,Google 开源了其 TCP BBR 拥塞控制算法,并提交到了 Linux 内核,从 4.9 开始,Linux 内核已经用上了该算法.根据以往的传统,Google 总是先在自家的生产环境上线运 ...

  5. H5_0024:对于事先无法确定css大小的情况,可以通过JS动态修改

            $(function(){             function Heights(){                 var WinH = $(window).height(); ...

  6. 在VS的依赖项中引用项目

    操作步骤:鼠标右击项目(注意是项目)->添加->引用->项目(在项目列表中选择需要引用的项目)->确定

  7. 使用mongoose--写接口

    定义数据模型 import mongoose from 'mongoose' mongoose.connect('mongodb://localhost/edu') const advertSchem ...

  8. 剑指offer-面试题21-调整数组顺序使奇数位于偶数前面-双指针

    /* 题目: 调整数组顺序使奇数位于偶数前面. */ /* 思路: 双指针: 一个指针last用于遍历,当为奇数时+1, 当为偶数时,交换last和pre指向的值,向前移动pre指针. */ #inc ...

  9. R 拼接结果展示

    学长教的拼接结果展示 哇,R 简直太有魅力了! 晚一点补充

  10. 二分类模型之logistic

    liner classifiers 逻辑回归用在2分类问题上居多.它是一个非线性的回归模型,其最大的好处恰恰是可以解决二元类问题,目前在金融行业,基本都是使用Logistic回归来预判一个用户是否为好 ...