(A卷,100分)- 对称美学(Java & JS & Python)

题目描述

对称就是最大的美学,现有一道关于对称字符串的美学。已知:

  • 第1个字符串:R
  • 第2个字符串:BR
  • 第3个字符串:RBBR
  • 第4个字符串:BRRBRBBR
  • 第5个字符串:RBBRBRRBBRRBRBBR

相信你已经发现规律了,没错!就是第 i 个字符串 = 第 i – 1 号字符串取反 + 第 i – 1 号字符串;

取反(R->B, B->R);

现在告诉你n和k,让你求得第n个字符串的第k个字符是多少。(k的编号从0开始)

输入描述

第一行输入一个T,表示有T组用例;

解析来输入T行,每行输入两个数字,表示n,k

  • 1 ≤ T ≤ 100;
  • 1 ≤ n ≤ 64;
  • 0 ≤ k < 2^(n-1);

输出描述

输出T行表示答案;

输出 "blue" 表示字符是B;

输出 "red" 表示字符是R。

备注:输出字符串区分大小写,请注意输出小写字符串,不带双引号。

用例

输入 5
1 0
2 1
3 2
4 6
5 8
输出 red
red
blue
blue
blue
说明 第 1 个字符串:R -> 第 0 个字符为R
第 2 个字符串:BR -> 第 1 个字符为R
第 3 个字符串:RBBR -> 第 2 个字符为B
第 4 个字符串:BRRBRBBR -> 第 6 个字符为B
第 5 个字符串:RBBRBRRBBRRBRBBR -> 第 8 个字符为B
输入 1
64 73709551616
输出 red
说明

题目解析

上图所示,是第1~6个字符串,可以发现第6个已经很长了,那么本题的最多要求到第64个字符串,那么有多长呢?答:2^63,即 2^(n-1)。这个长度如果用字符串来存储的话,肯定爆内存,因此任何需要缓存字符串的动作都是禁止的。

我们只能找规律,来通过规律推导出第n个字符串的第k个字符。

那么规律是啥呢?

如上图黄框所示,我们可以发现,

第6个字符串的后半部分,和第5个字符串完全相同;

同理,

第5个字符串的后半部分,和第4个字符串完全相同;

第4个字符串的后半部分,和第3个字符串完全相同;

第3个字符串的后半部分,和第2个字符串完全相同;

第2个字符串的后半部分,和第1个字符串完全相同;

因此,如果我们要找到的k位于第n个字符串的后半部分,假设为get(n, k),那么其等价于 get(n-1, k – 2^(n-2)),按此逻辑递归,就可以一直找到第2个字符串或第1个字符串,而这两个字符串很好确认,分别是”R“,”BR“。

那么如果我们要找到第k个字符串,位于第n个字符串的前半部分呢?

可以发现,其实get(n,k),如果k <= 2^(n-2) ,则相当于 get(n-1, k) 的颜色取反。

本题的精度问题

根据一位考友反馈的100%Java代码来看,逻辑和本题一模一样,唯一的区别在于:

这个考友分析是可能存在超过64长度的字符串,因此需要使用更大范围的double类型。

但是之后又有一个Python语言的考友反馈,这种解法无法拿到100%,还是8%。这里给大家解释一下,Python天生支持大数(只要不进行除法,非整除),因此对于Python老解法,我未作任何改动,如下图所示

 但是最后通过率只有8%。

不应该呀。为什么呢?

左思右想,发现,是对half的取值逻辑存在差异。

Java的是基于Math.pow(2, n-2)取得half,而Python是1 << n-2取得的half值,这两者有区别吗?

在逻辑上是没有区别的,但是实际上,当结果超过一定范围后,Math.pow会做四舍五入,即丢失精度

Java如下图所示,2^58开始丢失精度,即Math.pow做了四舍五入

 Python如下图所示,从2^55开始丢失精度,即Math.pow做了四舍五入

JS如下图所示,从2^55开始丢失精度,即Math.pow做了四舍五入

因此Math.pow和<<并不是等价的。

而本题需要使用Math.pow取half才能拿到满分。

但是,本题其实使用<<得到的答案才更加准确。

当然,到这里,本题的问题并没有解决,如下图所示

 

 

该场景是想求解第64个字符串的第4611686018427387905个字符的颜色。

即k = 4611686018427387905,half = math.pow(2, 62)

可以发现,不同的语言,这里产生的结果不同。其中JS返回的是1,而Java和Python返回的是0。

为啥呢?

Java

 

 

 JS

 

Python

 

  

  

 

因此,其实我们要对输入的k值,转成双精度浮点型,来满足和math.pow的结果匹配。

这里Java和Python都是OK的。但是JS就苦逼了。因为JS没有大数+浮点的类型。

因此,本题Java和Python的代码可以妥协来适配满分。但是JS无法适配。

建议使用JS的同学遇到这题,可以采用偷分策略,即不需要任何算法,直接输入red或者blue,选择分值更高。

JavaScript算法源码

需要注意的是,本题中

  • 1 ≤ n ≤ 64;
  • 0 ≤ k < 2^(n-1);

也就说 k 最大值可以取到 2^63 – 1,即9223372036854775807,而这个数已经超过了JS的number类型的安全数范围,即-2^53 + 1 ~ 2^53 – 1,超范围的数会得到不准确的值,比如

此时,我们应该考虑是BigInt代替Number类型来处理大数,而由于BigInt大数只能和BigInt大数进行运算,因此需要将程序中所有的数值全部变为BigInt类型。对于字面量数值,需要在字面量后面加个n,比如1是Number类型,那么1n就是BigInt类型的数值1。 

/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
 
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
 
const lines = [];
let t;
rl.on("line", (line) => {
  lines.push(line);
 
  if (lines.length === 1) {
    t = lines[0] - 0;
  }
 
  if (t && lines.length === t + 1) {
    lines.shift();
    const arr = lines.map((line) => line.split(" ").map(BigInt));
    getResult(arr);
 
    lines.length = 0;
  }
});
 
function getResult(arr) {
  for (let [n, k] of arr) {
    console.log(getNK(n, k));
  }
}
 
function getNK(n, k) {
  // 题目没说异常如何处理,因此我默认输入无异常,比如n=1的话,则k只能取0
  if (n === 1n) {
    return "red";
  }
 
  // n=2的话,则k只能取0或1
  if (n === 2n) {
    if (k === 0n) return "blue";
    else return "red";
  }
 
  // 第n个字符串的一半长度half
  let half = 1n << (n - 2n);
 
  if (k >= half) {
    return getNK(n - 1n, k - half);
  } else {
    return getNK(n - 1n, k) === "red" ? "blue" : "red";
  }
}

Java算法源码

需要注意的是,本题中

  • 1 ≤ n ≤ 64;
  • 0 ≤ k < 2^(n-1);

也就说 k 最大值可以取到 2^63 – 1,即9223372036854775807,而这个数刚好是Java的long类型最大值,因此不会造成整型溢出。但是我们代码中所有的数都必须使用long类型装载。

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    int t = sc.nextInt();

    double[][] arr = new double[t][2];
    for (int i = 0; i < t; i++) {
      arr[i][0] = sc.nextDouble();
      arr[i][1] = sc.nextDouble();
    }

    getResult(arr);
  }

  public static void getResult(double[][] arr) {
    for (double[] nk : arr) {
      System.out.println(getNK(nk[0], nk[1]));
    }
  }

  public static String getNK(double n, double k) {
    if (n == 1) {
      return "red";
    }

    if (n == 2) {
      if (k == 0) return "blue";
      else return "red";
    }

    double half = Math.pow(2, n - 2);

    if (k >= half) {
      return getNK(n - 1, k - half);
    } else {
      return "red".equals(getNK(n - 1, k)) ? "blue" : "red";
    }
  }
}

Python算法源码

需要注意的是,本题中

  • 1 ≤ n ≤ 64;
  • 0 ≤ k < 2^(n-1);

也就说 k 最大值可以取到 2^63 – 1,即9223372036854775807

关于Python处理大数的说明

即Python3支持任意大的整数运算。

因为本题中不涉及除法,因此无需额外处理。

# 输入获取
import math

t = int(input())
# 注意这里需要转float
arr = [list(map(float, input().split())) for i in range(t)]


# 算法入口
def getResult(arr):
    for n, k in arr:
        print(getNK(n, k))


def getNK(n, k):
    # 题目没说异常如何处理,因此我默认输入无异常,比如n=1的话,则k只能取0
    if n == 1:
        return "red"

    # n=2的话,则k只能取0或1
    if n == 2:
        if k == 0:
            return "blue"
        else:
            return "red"

    # 第n个字符串的一半长度half
    half = math.pow(2, n - 2)

    if k >= half:
        return getNK(n - 1, k - half)
    else:
        return "blue" if getNK(n - 1, k) == "red" else "red"


# 调用算法
getResult(arr)

免责声明:

1、IT资源小站为非营利性网站,全站所有资料仅供网友个人学习使用,禁止商用
2、本站所有文档、视频、书籍等资料均由网友分享,本站只负责收集不承担任何技术及版权问题
3、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除下载链接并致以最深的歉意
4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
5、一经注册为本站会员,一律视为同意网站规定,本站管理员及版主有权禁止违规用户
6、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和IT资源小站的同意
7、IT资源小站管理员和版主有权不事先通知发贴者而删除本文

0

评论0

站点公告

没有账号?注册  忘记密码?