概述
本题目80分容易,满分的关键在于要把时间复杂度从O(m*n)降为O(m+n)。题目版权归CCF所有,真题跳转官网查看。
真题来源:坐标变换(其二)
官网地址:www.cspro.org(模拟考试入口)
题目分析
对 m 个坐标 (x,y) 在给定含拉伸和旋转的 n 个操作中,选择从第 i 步操作到第 j 步操作完成 j-i+1 个操作,其中 1 <= i <= j <= n ,并输出最终结果。
在细心审题后发现:1、所有操作,都是基于初始坐标 (x,y) 进行操作的。2、拉伸和旋转两种操作互不影响。
方法一:暴力题解
- 接受n个操作,将其转为列表形式并存储在操作列表 n_line中
- 接受m个坐标的操作范围及其坐标,分别对应到i, j, x, y
- 对操作列表按照操作范围进行切片得到操作集 n_line[i-1:j]
- 遍历操作集,得到最终的拉伸系数k和旋转弧度theta
- 记录拉伸后的坐标为 (x0,y0) ,根据旋转公式:
x = x0 · cos(theta) - y0 · sin(theta)
y = x0 · sin(theta) + y0 · cos(theta) - 得到最终坐标后,格式化输出。
具体实现
from math import sin, cos
n, m = map(int, input().split())
n_line = []
for t in range(n):
n_line.append(list(map(float, input().split()))) # 操作列表 eg:[[1.0,0.5],[2.0,5.5]]
for t in range(m):
i, j, x, y = map(int, input().split()) # 操作范围及初始坐标
lines = n_line[i-1:j] # 操作集
theta = 0 # 初始弧度
for line in lines:
if line[0] == 1:
k = line[1]
x *= k
y *= k
elif line[0] == 2:
theta += line[1]
x0 = x # 拉伸后的x坐标
y0 = y # 拉伸后的y坐标
x = x0*cos(theta) - y0*sin(theta) # 最终x
y = x0*sin(theta) + y0*cos(theta) # 最终y
lines.clear() # 释放操作集
print("{} {}".format(x, y))
提交结果
方法二:满分题解
在具体叙述之前,先了解一下降低时间复杂度的方法有哪些。
降低复杂度的方法
将时间复杂度从O(m * n)降低到O(m + n)的办法通常涉及到改变算法的策略,以便更有效地利用输入数据,可能的策略如下:
- 使用更有效的算法:有些算法在处理特定问题时比其他算法更有效。例如,如果你正在对两个列表进行搜索,使用哈希表(HashMap)而不是线性搜索可以大大提高效率。
- 预处理:在某些情况下,你可以在运行主要算法之前对输入进行预处理。例如,如果你正在对一个列表进行排序,并需要在随后的搜索中使用这个列表,那么预先对列表进行排序可能会使搜索更快。
- 空间换时间:有时,你可以通过使用更多的内存来提高算法的效率。例如,如果你正在处理大量数据,并且需要多次访问这些数据,那么将数据存储在缓存或哈希表中可能会使访问速度更快。
- 分治策略:将问题分解成更小的子问题,然后分别解决这些子问题,最后将结果合并起来。这种策略可以有效地降低时间复杂度。
- 利用数据结构:不同的数据结构有着不同的操作复杂度。例如,如果需要频繁地查找和修改数据,那么使用哈希表可能比使用数组更有效。
- 懒加载:只在需要时才进行计算或处理,而不是一开始就处理所有的数据。这种方法可以节省计算资源并降低时间复杂度。
- 并行化:如果算法可以并行运行,那么你可以利用现代计算机的多核性能来降低时间复杂度。
题解详述
- 由于拉伸和旋转两种操作互不影响。可结合分治的思想,分别对拉伸、旋转操作进行各自的叠加处理。
- 利用两个列表,分别记录拉伸系数k、弧度theta在每次操作叠加后的值:拉伸系数的变化是累积,弧度的变化是累加。所以,可记录初始拉伸系数为1,初始弧度为0。
- 接受 n 个操作。当接受的操作为拉伸,则将上个操作累积的拉伸系数 ✖️ 本次操作的拉伸系数 添加到 拉伸系数列表 k_li 中,并将上个操作累加的弧度 添加到 弧度列表 theta_li 中;
当接受的操作为旋转,则将上个操作累加的弧度 ➕ 本次操作的弧度 添加到 弧度列表 theta_li ,并将上个操作累积的拉伸系数 添加到 拉伸系数列表 k_li 中。 - 接受 m 个坐标的操作范围及其坐标。由于在 theta_li 和 k_li 中分别 加入了初始值 ,对每个坐标 (x0,y0) 进行i ~ j 步操作:
最终拉伸系数k = 第 j 步的累积拉伸系数k_li[j] ➗ 第 i-1 步的拉伸系数k_li[i-1]
最终弧度theta = 第 j 步的累加弧度theta_li[j] ➖ 第 i-1 步的弧度theta_li[i-1] - 最后,计算最终坐标 (x,y) 并格式化输出。
具体实现
from math import sin, cos
n, m = map(int, input().split())
k_li = [1.0] # 定义拉伸系数累积列表并赋初值
theta_li = [0] # 定义弧度累加列表并赋初值
for t in range(n):
_type, _data = map(float, input().split()) # 获取操作类型 操作值
if _type == 1:
k_li.append(k_li[t] * _data) # 累积
theta_li.append(theta_li[t])
elif _type == 2:
k_li.append(k_li[t])
theta_li.append(theta_li[t] + _data) # 累加
for t in range(m):
i, j, x0, y0 = map(int, input().split()) # 操作范围及坐标
k = k_li[j] / k_li[i-1] # 计算最终拉伸系数
theta = theta_li[j] - theta_li[i-1] # 计算最终弧度
x0 *= k
y0 *= k
x = x0*cos(theta) - y0*sin(theta)
y = x0*sin(theta) + y0*cos(theta)
print("%f %f" % (x, y))