silentselene 发布的文章

事情的起因要追溯到四五年前,和同学摸索出了创建..文件夹和.文件夹的方法,并且发现了其具有一些特殊的特性。最近上班摸鱼想起这事来就想复现当时的实现方式,并在此基础之上发现了一些拓展的特性。

...文件夹

稍微对Linux系统有点了解的都知道,.文件夹表示的是当前目录,..文件夹表示的是上级目录,在Windows中亦是如此。

在四五年前所摸索出来的创建...文件夹实际上并不是...,而是. , . .., .. ,即在前后有一个空格的,看起来像...文件夹的文件夹。

感兴趣的读者可以尝试在cmd中进入想要的目录输入以下命令并实际点开看看效果:

mkdir "\ ..\ "

以上命令创建的是 ..文件夹,即在最前面有一个空格的..文件夹。第一次进入时可以发现仍然在当前文件夹中,第二次进入时资源管理器会提示位置不可用,并且尝试对其进行删除、重命名操作都会提示操作无法完成。这个命令我推测是四五年前所发现的命令,它与下面这两个更易于理解的命令等价:

mkdir "./ ../"
mkdir " ../"

顺便说一句,如果直接尝试以下命令是不会成功的,推测创建时会截断末尾的dot与空格符号

mkdir " .."
mkdir "./ .."

会提示拒绝访问或者文件名、目录名或卷标语法不正确。

如果想要删除这个文件夹可以使用rmdir命令。

具有类似特性的还有 文件夹,即空格文件夹。

小结
. , ., .. , .., 文件夹
普通方式(即右键新建文件夹方式)无法创建
可使用类似mkdir "./.. /"方式创建
可使用类似rmdir "./.. /"方式删除
可使用类似rename "example" "./.. /"方式将其他文件夹重命名为特殊文件夹,但是本操作是不可逆的,反过来会提示系统找不到指定文件
在资源管理器中对其进行删除、重命名操作会提示操作无法完成,对其进行修改属性操作会无法保存
第一次双击进入文件夹会相当于没进,此时再次双击进入会提示目录不可用或进入一个无法操作的空文件夹

...文件夹

...文件夹与..文件夹的区别在于它是可以实际创建出来的,不需要添加先导与末尾空格,并且...文件夹与.........等大于三个.的文件夹性质相同,与上面类似,使用如下命令创建与删除,ren的命令特性也相似:

mkdir ".../"
rmdir ".../"

它与..文件夹在特性上的主要区别在于,..文件夹打开第二次就到头了(第二次还不一定打得开),而...系的文件夹可以打开无穷次(其实不一定有无穷次,取决于文件夹路径长度限制),不管打开多少次,始终打开的都是它所在的文件夹。

尝试对其进行删除取决于该文件夹所在位置,可能会导致资源管理器崩溃。如果它在磁盘根目录,相当于对删除磁盘,会提示操作不合法,如果它不在磁盘根目录,会导致资源管理器在检索文件时访问无穷层级导致崩溃。

小结
...与所有三个.以上的文件夹
普通方式(即右键新建文件夹方式)无法创建
可使用类似mkdir "./.../"方式创建
可使用类似rmdir "./.../"方式删除
可使用类似rename "example" "./.../"方式将其他文件夹重命名为特殊文件夹,但是本操作是不可逆的,反过来会提示系统找不到指定文件
在资源管理器中对其进行重命名操作会提示操作无法完成,对其进行修改属性操作会无法保存
可以无穷此进入该文件夹,进入的始终是其所在的文件夹
若其不在磁盘根目录,在资源管理器中对其进行删除操作可能会导致资源管理器崩溃,若其在磁盘根目录会提示文件夹不可用(实际的删除操作是对磁盘进行)

exampleexample..文件夹

在这里的example并不是特指example这个单词,而是指任意正常文件夹的前缀,下文中均用example代替。

假设当前文件夹下不存在example文件夹,若正常右键创建example..文件夹会被自动重命名为example文件夹,若使用类似上文中的方法使用mkdir "./example../"才可以成功创建,当然删除的方法也类似。

example文件夹不存在时,在资源管理器中对其的所有操作均是无效的,双击进入会提示位置不可用,对其进行重命名、删除与修改属性均会提示没有找到项目。

example文件夹存在时,对其所有操作实际上均是对example文件夹生效,包括但不限于删除,重命名,修改属性与双击打开,包括在命令行中使用ren进行重命名亦是如此。

小结
example..文件夹
普通方式(即右键新建文件夹方式)无法创建,会创建成example文件夹
可使用类似mkdir "./example../"方式创建
可使用类似rmdir "./example../"方式删除
可使用类似rename "example" "./example../"方式将其他文件夹重命名为特殊文件夹,但是本操作是不可逆的,反过来会对example文件夹进行操作,若其不存在会提示系统找不到指定文件
example文件夹不存在,在资源管理器中对其进行重命名、打开、删除、修改属性等操作会提示文件夹不存在
example文件夹存在,在资源管理器中对其进行重命名、打开、删除、修改属性等操作会对example文件夹生效,甚至于在命令行中使用ren命令亦是如此,使用move命令则会提示系统找不到指定的文件

设备文件夹

设备文件夹相比以上几种文件夹更为特殊,其是早期dos系统中用于设备的保留字,设备文件夹包括以下几种:CON, PRN, AUX, NUL, COM1, COM2,..., COM9, LPT1, LPT2,..., LPT9

虽然在资源管理器中无法直接创建,但仍可以用与上述类似的方法创建与删除,据说在早期Windows中创建会导致系统蓝屏

根据创建的文件夹名字的不同,包括但可能不限于不存在/函数无效/句柄无效几种类型的特性。

小结
CON, PRN, AUX, NUL, COM1, COM2,..., COM9, LPT1, LPT2,..., LPT9文件夹
普通方式(即右键新建文件夹方式)无法创建,会提示指定的设备名无效
可使用类似mkdir "./con/"方式创建
可使用类似rmdir "./con/"方式删除
对于PRN, AUX, COM1, COM2,..., COM9, LPT1, LPT2,..., LPT9文件夹,创建之后对文件夹本身的任何操作会提示没有找到项目、文件不存在等,而在这些文件夹中可以正常地创建文件夹,并且在新创建的文件夹中可以正常地使用与存放文件
对于con文件夹,对其进行包括打开的任何操作会提示句柄无效或无反应
对于nul文件夹,对其打开、修改属性操作会提示函数不正确,删除操作无效,重命名操作会提示文件过大

类标识文件夹

这类文件夹相比上述的文件夹,对于正常的系统来说实用性可能就高很多了,并且不需要命令行,直接右键重命名即可创建。

还是以example文件夹为例,直接右键重命名为example.{645FF040-5081-101B-9F08-00AA002F954E}文件夹,保存刷新即可。可以看到它变成了回收站的图标,并且打开也是回收站,而且该文件及可以进行正常地重命名、删除、修改属性等操作,此外这种方法还可以与设备文件夹结合使用。

最好用的一点是它可以在cmd中使用ren命令还原成普通的文件夹,这是与上面几点文件夹相比最具使用价值的优点(指藏东西)。如果在其中放入一个...文件夹还可以屏蔽对该文件夹的删除操作(导致资源管理器崩溃,删除失败),相信老司机们应该明白这玩意可以怎么用了。

该类文件夹的原理是利用了Windows类标识符来伪装文件夹,类似的类标识符还有:

参考资料:类标识符
我的电脑 {20D04FE0-3AEA-1069-A2D8-08002B30309D}
我的文档 {450D8FBA-AD25-11D0-98A8-0800361B1103}
拨号网络 {992CFFA0-F557-101A-88EC-00DD010CCC48}
控制面板 {21EC2020-3AEA-1069-A2DD-08002B30309D}
计划任务 {D6277990-4C6A-11CF-8D87-00AA0060F5BF}
打印机 {2227A280-3AEA-1069-A2DE-08002B30309D}
记事本 {1FBA04EE-3024-11D2-8F1F-0000F87ABD16}
网络邻居 {208D2C60-3AEA-1069-A2D7-08002B30309D}
回收站 {645FF040-5081-101B-9F08-00AA002F954E}
公文包 {85BBD920-42A0-1069-A2E4-08002B30309D}
字体 {BD84B380-8CA2-1069-AB1D-08000948F534}
Web 文件夹 {BDEADF00-C265-11d0-BCED-00A0C90AB50F
“上帝模式”{ED7BA470-8E54-465E-825C-99712043E01C}

更进一步

就目前来说最具有实用价值的只有类标识文件夹与...文件夹的结合使用,其他文件夹大多因为在cmd中无法还原为普通文件夹而无法存储文件。

但是

虽然cmd辣鸡,但是我们有无敌的Git Bash啊。

以上所有文件夹在Git Bash中均可以很轻松地进行创建、修改、重命名、访问等操作,由此使得上述所有文件夹均可以还原为普通的文件夹,使其真正具有了隐藏文件的价值。感兴趣的读者可以搜索linux下对文件的基本操作,套用到Git Bash中即可。

Git Bash 永远嘀神

本文起源于:swap 的扩容(及其他)

经常写简单命令行程序的人可能会知道,这些是标准输入输出文件和标准出错文件,在平时一般会被重定向到屏幕上,你可以看到它实际上指向的是/proc/self/fd/*

[root@quality-unicorn-3 ~]# ls /dev -alh | grep std
lrwxrwxrwx   1 root root          15 Feb  7 08:29 stderr -> /proc/self/fd/2
lrwxrwxrwx   1 root root          15 Feb  7 08:29 stdin -> /proc/self/fd/0
lrwxrwxrwx   1 root root          15 Feb  7 08:29 stdout -> /proc/self/fd/1

/proc/self/fd/*又是啥呢?
用同样的套路可以看到:

... /proc/self/fd/0 -> /dev/pts/0
... /proc/self/fd/1 -> /dev/pts/0
... /proc/self/fd/2 -> /dev/pts/0

实际上在这里的self指的就是程序自身,也就是当前所使用的bash程序,而下面fd中的0,1,2等就是对应打开文件的符号链接,它们都指向了同一个设备:/dev/pts/0。而利用secureCRT连接的设备代号即为pts/0

到这里为止一切都明白了,当我们使用终端连接的时候,在操作系统中创建了一个设备pts/0,而我们所做的所有交互都是通过这个设备进行。
stdin/stdout/stderr都被重定向至这个设备中,也就意味我们可以通过这个设备来给连接时创建的bash发送与接受命令。使用ps -A | grep bash可以看到我们启动的bash所对应的设备描述文件,即我们自己的窗口

[root@quality-unicorn-3 ~]# ps -A |grep bash
 3503 pts/0    00:00:00 bash

如果有很多个人(或者你自己开很多个窗口)同时连接,就可以看到类似如下的内容:

[root@quality-unicorn-3 ~]# ps -A |grep bash
 3503 pts/0    00:00:00 bash
 3536 pts/1    00:00:00 bash

既然我们已经知道pts/*究竟代表什么,就可以玩一些骚操作了,比如:

[root@quality-unicorn-3 ~]# echo cnm > /dev/pts/1

这样就可以往另一个人的界面中发送文字片段,或者:

[root@quality-unicorn-3 ~]# cat /dev/pts/1

这样别人输入的命令就有概率出现在你的屏幕上(有概率是因为他的输入要么出现在他自己的屏幕上,要么出现在你的屏幕上,两边不会同时出现,测试发现基本是等概率的),导致你能猜到他要输入什么信息,并且他经常需要按好几次按键才会成功。还可以把以上两点结合起来,作用读者可以自己尝试:

[root@quality-unicorn-3 ~]# cat /dev/pts/1 > /dev/pts/1 

顺便说一句,如果不是使用ssh远程连接,在这里的pts/*实际上应该为tty/*,具体背景可以在参考资料里找。

参考资料:
stdin、stdout、stderr 标准流本质上到底是什么? - 知乎
Linux下的tty和pts详解

背景

当前这个网站所使用的服务器是搬瓦工的远古套餐,RAM只有可怜的512Mb,默认的swap只有可怜的132MB,连阿里云的学生机配置都是它的四倍,上面还用LAMP搭建了好几个网站(虽然日活不高就是了),以至于每次登录到管理平台时都能看到RAM和SWAP的条条不是红的就是黄的。(这也是为啥网站这么简陋的原因)

服务器刚刚重启过,现在还是绿的,而且swap我也改过了,这是另一台的截图,莫得原先满内存的截图了

(服务器刚刚重启过,现在还是绿的,而且swap我也改过了,这是另一台的截图,莫得原先满内存的截图了)

虽然不确定是不是极低内存带来的后果,我的网站每隔几个月就会挂一次,每次都要登陆上去重启下,推测是极低内存导致的

重启记录

重启记录

既然莫得钱去升级物理机,只能从swap的角度入手了,网上搜了搜方法顺便学了下Linux的皮毛,在此记录下来,顺便希望扩容swap之后不会再出现挂掉之类的问题。

正文

由于写这篇文章的时候搭本网站的机子已经成功扩容了,以下内容是从另一台机子重新做起的,依然是搬瓦工上同配置的乞丐机:

登陆上去后首先输入free -h可以看到现在机器的内存与swap信息:

[root@quality-unicorn-3 ~]# free -h
              total        used        free      shared  buff/cache   available
Mem:           496M        121M        115M         29M        258M        332M
Swap:          131M        2.5M        129M

使用swapon --show可以查看当前swap对应的文件

[root@quality-unicorn-3 ~]# swapon --show
NAME  TYPE SIZE USED PRIO
/swap file 132M 2.5M   -2

想要扩容swap可以创建一个新的swap,或者增加已有的空间,这里以扩容为例:

[root@quality-unicorn-3 ~]# swapoff /swap
[root@quality-unicorn-3 ~]# dd if=/dev/zero of=/swap bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 6.87611 s, 156 MB/s
[root@quality-unicorn-3 ~]# mkswap /swap
Setting up swapspace version 1, size = 1048572 KiB
no label, UUID=fdd6a949-d585-4c31-af70-d3fbe0212a64
[root@quality-unicorn-3 ~]# swapon /swap

依次输入上面的命令就可以啦,可以使用freefree -h查看结果:

[root@quality-unicorn-3 ~]# free
              total        used        free      shared  buff/cache   available
Mem:         507912      124440       14688       30692      368784      339816
Swap:       1048572           0     1048572
[root@quality-unicorn-3 ~]# free -h
              total        used        free      shared  buff/cache   available
Mem:           496M        121M         14M         29M        360M        327M
Swap:          1.0G          0B        1.0G

当然,这里使用的机器是没怎么使用内存的,如果内存本来就爆表了怎么办呢?

以下我来模拟原本内存就写满了的情况:

[root@quality-unicorn-3 memtest]# free -h
              total        used        free      shared  buff/cache   available
Mem:           495M         38M         19M        1.5M        436M         42M
Swap:          130M         76M         54M

如上所示,当前swap只剩54M,物理内存就更少了,这种情况下是不能直接删除swap的,因为swap中存储的东西不足以装入物理内存中,所以只能创建一个新的临时的swap暂时存储,再扩容原先的swap。以下是全过程:

[root@quality-unicorn-3 ~]# dd if=/dev/zero of=/swaptemp bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 6.39658 s, 168 MB/s
[root@quality-unicorn-3 ~]# mkswap /swaptemp
Setting up swapspace version 1, size = 1048572 KiB
no label, UUID=2c971653-47df-4a23-a0ac-507d9e382a9f
[root@quality-unicorn-3 ~]# chmod 0600 /swaptemp
[root@quality-unicorn-3 ~]# swapon /swaptemp
[root@quality-unicorn-3 ~]# swapoff /swap
[root@quality-unicorn-3 ~]# dd if=/dev/zero of=/swap bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 7.40158 s, 145 MB/s
[root@quality-unicorn-3 ~]# mkswap /swap
Setting up swapspace version 1, size = 1048572 KiB
no label, UUID=9f56cb48-ab69-4329-a81b-de4a98162e28
[root@quality-unicorn-3 ~]# swapon /swap
[root@quality-unicorn-3 ~]# swapoff /swaptemp
[root@quality-unicorn-3 ~]# rm /swaptemp
rm: remove regular file ‘/swaptemp’? y

这样swap就扩容完啦,以下是扩容后的结果:

[root@quality-unicorn-3 ~]# free -h
              total        used        free      shared  buff/cache   available
Mem:           495M         54M        6.3M        1.5M        434M         26M
Swap:          1.0G         65M        958M

可以看到swap已经被扩容至1G

知其所以然

  • free命令用于显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存,如果加上-h,即free -h可以使输出结果更友好,将容量转换为合适的单位。类似的用法在其他很多命令中也有,比如ls -lhls -l相比单位更友好,还有du -hdu也类似
  • mkswap/swapon/swapoff均用于管理交换内存(swap),mkswap用于设置交换区,swaponswapoff分别用于激活与关闭交换区。使用时直接在后面跟随对应的文件即可
    文中还出现了swapon --show的用法,其作用是列出当前所有的交换区
  • dd命令用于读取、转换并输出数据,在本文中仅仅读取并输出,并无转换的操作。对于本文中出现的dd if=/dev/zero of=/swap bs=1M count=1024命令,其作用是从/dev/zero中读取数据,存入/swap文件中,每次读取与输出1M大小的数据,读写1024次即1024M,也就是1G
  • chmod 0600 /swaptemp命令用于修改/swaptemp的权限,实际上这一步不是必须的,但是如果没有这一步可能会提示当前的权限(一般是0644)不安全,建议使用0600。所以为了安全性最好加上

多学点儿

在演示时我是怎么快速把内存填满的呢?

Linux有个很神奇的功能,是可以把一个文件夹挂载至内存当中,即往这个文件夹内部写入的文件都会被写入到内存中。所以只要根据这种方法创建一个文件夹往里不停写东西就行啦:

mkdir memtemp
mount -t ramfs ramfs memtemp/
dd if=/dev/urandom of=z/file bs=1M count=128
dd if=/dev/urandom of=z/file bs=1M count=128
...

不过这是一个非常危险的行为,与在应用程序内申请内存不同,这种方式往内存里写东西似乎并没有做很多保护措施,稍微写多一点你可能就连不上服务器了,每次写入文件时一定要注意别超过物理内存的容量,当接近写满时可以输入一些其他命令(比如yum/apt list啥的)令操作系统把一些内存往swap里装。

参考资料/扩展阅读:
在 Linux 上简单模拟系统负载的方法

在使用dd命令时用到的/dev/zero/dev/urandom是什么呢?

对Linux有那么一丁点儿了解的人可能会知道,在Linux中,万物皆为文件。多有一点儿了解的可能还会知道/dev目录是设备的目录。所以在该目录下的zerourandom当然就是设备啦。

不过设备可不一定必须是物理设备,在这里的zerourandom就是虚拟设备。

zero设备的作用是可以无限从中读取出空内容(0x00),而urandom则可以无限从中读取出随机数。

zero类似的还有null,可以无限往里写入内容,写入的内容会被丢弃,与urandom类似的还有random,不过random获取到的是来自于物理设备噪声的真随机数,而urandom获取到的是伪随机数。

除此之外还有一些设备也在/dev文件夹中,可以使用ls /dev -alh来查看,这里列举几个常用的:

stdin/stdout/stderr:本来是打算把全文就放在这里的,可是越写越长放在这里有点喧宾夺主了,于是另外开了一篇:stdin/stdout/stderr 那点事儿

full:总是在向其写入时返回设备无剩余空间(错误码为ENOSPC),读取时则与/dev/zero相似,返回无限的空字符(0x00)。这个设备通常被用来测试程序在遇到磁盘无剩余空间错误时的行为。

参考资料/拓展阅读:
linux dev 常见特殊设备介绍与应用(loop,null,zero,full,random)
/dev/urandom和/dev/random的区别是什么

问题背景

每次用windows to go在mac上跑,使用bootcamp装好驱动后D盘总是消失。在磁盘管理里重新设置盘符之后可以正常使用,但是重启之后又没了。对于习惯尽可能把软件装在D盘里的我来说很不方便,所以在查阅资料后找到了解决方法。

吐槽

网上很多使用diskpart的教程要么就是过期的,要么就是不知道哪里搞来的假教程,什么set id之类的全是假的,我搜索了好久,每次不是提示:

DISKPART> set id=07 override

指定的类型的格式不正确。
有关此命令类型的详细信息,请使用 HELP SET 命令

就是

DISKPART> set 07 override

为此命令指定的参数无效。
有关此命令类型的详细信息,请使用 HELP SETID 命令

还有设置注册表之类的,我对直接修改注册表向来是比较抵触的,因为随便修改底层的、自己完全不了解的东西是需要冒着极大的风险的,而且看着网上残次不齐抄来抄去的同一篇教程总是很不放心。

结论

  • win+R 开启 cmd
  • 命令提示符中输入 diskpart 将得到一个新的命令行窗口(此步骤可能需要获得管理员权限)
  • 使用 list disk 显示磁盘列表
DISKPART> list disk

  磁盘 ###  状态           大小     可用     Dyn  Gpt
  --------  -------------  -------  -------  ---  ---
  磁盘 0    联机              233 GB      0 B        *
* 磁盘 1    联机              472 GB      0 B        *
  • 使用 select disk 选择需要的磁盘
DISKPART> select disk 1

磁盘 1 现在是所选磁盘。
  • 使用list volume显示分卷列表
DISKPART> list volume

  卷 ###      LTR  标签         FS     类型        大小     状态       信息
  ----------  ---  -----------  -----  ----------  -------  ---------  --------
  卷     0         EFI          FAT32  磁盘分区         300 MB  正常         已隐藏
  卷     1     C                NTFS   磁盘分区         200 GB  正常         启动
  卷     2     D                exFAT  磁盘分区         271 GB  正常
  卷     3                      FAT32  磁盘分区         350 MB  正常         系统
  • 使用select volume选择分卷列表
DISKPART> select volume 2

卷 2 是所选卷。
  • 使用detail volume显示分卷信息
DISKPART> detail volume

  磁盘 ###  状态           大小     可用     Dyn  Gpt
  --------  -------------  -------  -------  ---  ---
* 磁盘 1    联机              472 GB      0 B        *

只读                   : 否
隐藏                   : 否
没有默认驱动器号       : 否
卷影副本               : 否
脱机                : 否
BitLocker 已加密       : 否
可安装            : 是

卷容量                 :  271 GB
卷可用空间             :   84 GB
  • 使用attribute volume set hidden来隐藏分卷,此时可以看到D盘(在我这里是D盘)在计算机中消失
DISKPART> attribute volume set hidden

卷属性设置成功。
  • 使用attribute volume clear hidden来显示分卷,此时可以看到D盘重新显示
DISKPART> attribute volume clear hidden

卷属性清除成功。

更进一步

  1. 顺便提一句,只读属性使用readonly字段设置,设置了只读之后除格式化外任何修改磁盘的操作会提示该卷被写保护。
  2. diskpart还可以进行磁盘管理中可做的对磁盘的任何操作,比如分卷、扩展等,功能还更多(我怀疑自带的磁盘管理底层用的就是diskpark)

又是吐槽

windows的命令行提示真的做的和屎一样,可设置的属性字段各种help的都不提示,可能对于英文用户来说这些属性可能是显而易见用英文标识的,但对于中文用户还要在不同的同义词中去猜程序具体使用的是哪一个。如果不能像 attribute volume set 隐藏 这样使用中文来输入命令,要么就加上英文字段,要么就干脆不要翻译,反正一般人也不会用这些命令。

先上一张最终的结果:

Screenshot 2019-04-08 at 08.03.06.png

这次资格赛总的来说表现的还不错,可惜第一题没仔细读题处理大数据出了问题。

Foregone Solution

  • 题目链接
    Foregone Solution
  • 题意
    将一个数字分成两个数字相加,使得这两个数字任何一位均不为4。其中有1分该数据不超过1e100,其他数据不超过1e9。
  • 分析
    将数字逐位处理,如果遇到4拆分成两个部分,如果不是4直接给某一个答案加上这一位即可。
  • 代码
    (本段代码未特别处理大数据)
#include <bits/stdc++.h>
using namespace std;
void slove(int n){
    int temp = 1;
    int ans1=0,ans2=;
    while (n){
        if (n%10==4){
            ans1+=2*temp;
            ans2+=2*temp;
        }
        else ans1+=n%10*temp;
        n/=10;
        temp*=10;
    }
    cout<<ans1<<' '<<ans2;
    return ;    
}
int main(){
    int t;
    cin>>t;
    for (int i=1;i<=t;i++){
        int n;
        cin>>n;
        cout<<"Case #"<<i<<": ";
        slove(n);
        cout<<endl;
    }
}

You Can Go Your Own Way

  • 题目链接
    You Can Go Your Own Way
  • 题意
    在N*N的网格中,每次只能向右或者向下走,当前已经规划出一条路径,请你设计出一个没有任意一段完全相同的路径(没有任意一段完全相同指对于已知路径中任意的A->B,设计的路径也不能有A->B,但是A与B本身均是可以到达的)
  • 分析
    乍一看要设计出很复杂的递归回溯算法什么的,但是分析后发现,只要将读入数据的E与S对调即可。在对角线之外的情况路线完全分离故可行,在对角线之中的情况仅有:交错与途径同一点组成十字两种情况,故该种做法可行。
  • 代码
#include <bits/stdc++.h>
using namespace std;
int main(){
    int t;
    cin>>t;
    for (int i=1;i<=t;i++){
        int nn;
        cin>>nn;
        string s;
        cin>>s;
        printf("Case #%d: ",i);
        for (auto i:s){
            if (i=='S')printf("E");
            else printf("S");
        }
        printf("\n");
    }
}

Cryptopangrams

  • 题目链接
    Cryptopangrams
  • 题意
    本题描述了一种加密方法。首先将26个字母映射到26个不同的素数(规模不超过1e100)上,其中A对于的素数最小,Z对应的素数最大,以此类推。然后将要加密的文本两两相邻的字母所对应的素数相乘,对于长度为k的字符串,可以生成长度为k-1的数字序列。现给你这个数字序列,求还原后的字符串,保证还原后的字符串26个字母至少各出现一次。
  • 分析
    最开始被两个素数相乘这个条件给蒙骗了,以为需要将大数分解成两个素数,经思考后发现对于序列中相邻的两个数实际上为(x*y)和(y*z)因此只需任意一对数的最大公约数后即可求出该处对应的素数,之后即可递推出所有位置进而处理出对应关系。但是还有一点需要注意:不能直接选取最开始的两个数字,它应付不了此类情况:ABA...这样求出的最大公约数并不是B而是AB。因此需要找到序列中任何一对不相同的数字来求最大公约数。
  • 代码
import java.math.BigInteger;
import java.util.Scanner;


public class Solution {
    private static BigInteger _0=BigInteger.valueOf(0);

    private static BigInteger gcd(BigInteger a, BigInteger b){
        if (b.compareTo(_0)==0){
            return a;
        }
        else {
            return gcd(b,a.mod(b));
        }
    }

    public static void main(String args[]) {

        Scanner scanner = new Scanner(System.in);
        int t;
        t=scanner.nextInt();
        for (int ii = 1; ii <= t; ii++) {
            scanner.nextBigInteger();
            int l=scanner.nextInt();

            System.out.printf("Case #%d: ",ii);

            BigInteger []input=new BigInteger[200];
            BigInteger []ans=new BigInteger[200];
            for (int i=0;i<l;i++){
                input[i]=scanner.nextBigInteger();
            }

            int pos = 0;
            for (int i=0;i<l;i++){
                if (input[i].compareTo(input[i+1])!=0){
                    ans[i+1]=gcd(input[i],input[i+1]);
                    pos=i+1;
                    break;
                }
            }
            for (int i=pos-1;i>=0;i--){
                ans[i]=input[i].divide(ans[i+1]);
            }
            for (int i=pos+1;i<=l;i++){
                ans[i]=input[i-1].divide(ans[i-1]);
            }

            BigInteger []hashtable=new BigInteger[30];

            for (int i=0;i<=l;i++){
                for (int j=0;j<26;j++){
                    if (hashtable[j]==null){
                        hashtable[j]=ans[i];
                        break;
                    }
                    else {
                        final int i1 = hashtable[j].compareTo(ans[i]);
                        if (i1 ==0){
                            break;
                        }
                        else if (i1 >0){
                            for (int k=25;k>j;k--){
                                hashtable[k]=hashtable[k-1];
                            }
                            hashtable[j]=ans[i];
                            break;
                        }
                    }
                }
                if (hashtable[25]!=null)break;
            }

            for (int i=0;i<=l;i++){
                for (int j=0;j<26;j++){
                    if (hashtable[j].compareTo(ans[i])==0){
                        System.out.print((char)(j+(int)'A'));
                        break;
                    }
                }
            }
            System.out.print("\n");
        }
        scanner.close();
    }
}

Dat Bae

  • 题目链接
    Dat Bae
  • 题目大意
    本题为交互题。每组数据指定N,B,F三个数字,允许发送长度为N的01串,将返回去除B位,总长度为N-B的01串,求出丢失的是哪些位。其中N不超过1024,B不超过15,F最小为5。
  • 分析
    表面上看起来5次机会最多处理25种情况,而本题情况显然最大有1024取15种,远远大于25,但实际上这也是本题的障眼法。首先第一次依次输出16个1,16个0相间排列的询问,可以将处理1024长度的字符串分割成处理长度16的字符串。即使15个缺失位全部在同一区间内,长度16的1或0也可以将其分割开。之后输出8个1,8个0相间排列的询问,将原本长度本应为16的区间分成两部分处理,判断前8位缺失多少位,后8位缺失多少位,以此类推。最终通过16,8,4,2,1个连续01相间排列的串刚好共5次即可得到最终缺失的位数。
#include <bits/stdc++.h>
using namespace std;

void generate(int n,int l,char *a){
    int t=0,pos=0;
    while (pos!=n){
        if (pos/l%2) a[pos++]='0';
        else a[pos++]='1';
    }
    a[pos]=0;
}
string s[5];

void slove(string str,int ll,int l,int len,int now){
    if (str.length()==0){
        for (int i=0;i<len;i++){
            cout<<ll+i<<' ';
        }
    }
    else if (len==str.length()){
        return ;
    }
    else if (now==0){
        if (str[0]=='0')cout<<ll<<' ';
        else cout<<ll+1<<' ';
    }
    else if (str[str.length()-1]=='1'){
        int minn=min(1<<now,len);
        slove(s[now-1].substr(l,str.length()),ll,l,minn,now-1);
        slove("",ll+(1<<now),l+str.length(),len-minn,now-1);
    }
    else {
        for (int i=0;i<str.length();i++){
            if (str[i]=='0'){
                slove(s[now-1].substr(l,i),ll,l,1<<now,now-1);
                slove(s[now-1].substr(l+i,str.length()-i),ll+(1<<now),l+i,len-(1<<now),now-1);
                break;
            }
        }
    }
}

int main(){
    int t;
    cin>>t;
    while (t--){
        int n,b,f;
        cin>>n>>b>>f;
        char a[1100];
        for (int k=4;k>=0;--k){
            generate(n,1<<k,a);
            cout<<a<<endl;
            fflush;
            cin>>s[k];
        }
        int pre=0;
        int t=0;
        int tot=0;
        for (int i=1;i<s[4].size();i++){
            if (s[4][i]!=s[4][i-1]){
                slove(s[3].substr(pre,i-pre),tot,pre,16,3);
                pre=i;
                tot+=16;
            }
        }
        slove (s[3].substr(pre),tot,pre,n-b-pre+(b-(tot-pre)),3);
        cout<<endl;
        fflush;
        cin>>n;
        if (n==-1)break;

    }
}
/*
以下为参考测试数据
3
10 2 10
11111111
11111110
11100001
10011001
01010101
1
5 2 10
111
111
110
101
011
1
32 8 10
111111111111000000000000
111100000000111111110000
000011110000111100001111
110011001100110011001100
101010101010101010101010
1

ans:
0 3
0 9
0 1 2 3 28 29 30 31
*/