在工位摸鱼,遂把周赛打了,尝试用AI辅助写一写python。
A 小红的字符串处理
思路
签到,因为字符串相当于一个序列,所以可以用 ".".join(s) 来实现在序列中插入字符 .,这样码量少一点。
代码
python
s = input()
s = ".".join(s)
print(s)
B 小红的菊花构造
思路
签到,让 1 ∼ n 1\sim n 1∼n 中除了 k 以外的点往 k 上连一条边就行了。这题主要是为了介绍菊花树这个概念。
代码
python
def main():
import sys
data = sys.stdin.read().strip().split()
if not data:
return
try:
n = int(data[0])
k = int(data[1]) if len(data) > 1 else 0
except:
return
out_lines = []
for i in range(1, n+1):
if i == k:
continue
out_lines.append(f"{i} {k}")
sys.stdout.write("\n".join(out_lines))
if __name__ == '__main__':
main()
C D 小红的swap
思路
CD题目是一样的,只是 D 题需要操作次数最少,所以这题写一个 D 的代码就行了。
题面可能不是很好理解,字符 c 就是一个缓存区,一开始存储的字符 2,每次和字符串 s 交换一个字符。比如 s 是 0011,c 和 s 的第二个字符交换,就得到了 c:0 和 s:0211。最终目的是通过若干次交换,使得字符串 s 和 t 一样。
这个题保证了 s 和 t 有相同数量的 0 0 0 和 1 1 1,而且 p 一定大于等于操作次数最少的操作次数,因此不存在无解的情况,所以构造的时候不需要特判。
操作思路如下:首先因为 s 和 t 有相同数量的 0 0 0 和 1 1 1,因此 s 和 t 不同时, s i s_i si 为 0 0 0 但 t i t_i ti 为 1 1 1 的位置的个数一定等于 s i s_i si 为 1 1 1 但 t i t_i ti 为 0 0 0 的位置的个数。当我们交换 s 是 0 的位置时,c 就得到了 0,我们想要把它放到 s i s_i si 为 1 1 1 但 t i t_i ti 为 0 0 0 的位置上,位置 i i i 就相同了,同时 c 就得到了 1,同理再把这个 1 放到 s i s_i si 为 0 0 0 但 t i t_i ti 为 1 1 1 的位置上,以此类推。
所以我们就是要把 s i s_i si 为 0 0 0 但 t i t_i ti 为 1 1 1 的位置以及 s i s_i si 为 1 1 1 但 t i t_i ti 为 0 0 0 的位置统计出来,然后交换前者的某个位置,交换后者的某个位置的操作交替进行,最后再把一开始交换的位置(字符串 s 那里放着字符 c 一开始存放的字符 2)交换回来就可以了。
代码
python
import sys
def main():
data = sys.stdin.read().strip().split()
if not data:
return
# 读取整数 n(可忽略)和两个字符串 s, t
# 假设输入为: n s t
n = int(data[0])
if n == 0:
print(0)
return
if len(data) < 3:
return
# n = int(data[0])
s = data[1].strip()
t = data[2].strip()
a = [] # 在 s 中为 '0' 但在 t 中为 '1' 的下标(1-based)
b = [] # 在 t 中为 '0' 但在 s 中为 '1' 的下标(1-based)
L = min(len(s), len(t)) # 无意义,因为题目保证长度相同
for i in range(L):
if s[i] == '0' and t[i] == '1':
a.append(i+1) # 下标从 1 开始
elif t[i] == '0' and s[i] == '1':
b.append(i+1)
# 输出结果:2*len(a)+1,然后依次输出a、b,最后输出a的第一个内容
if a and len(a) == len(b):
print(2 * len(a) + 1)
for i in range(len(a)):
print(a[i])
print(b[i])
print(a[0])
else:
print(0)
if __name__ == '__main__':
main()
小红的分割线
思路
首先介绍一点点线性代数的知识,向量叉积的运算结果有正负之分。对于 a ⃗ × b ⃗ \vec a\times \vec b a ×b ,若结果为正,则表示向量 b ⃗ \vec b b 在向量 a ⃗ \vec a a 的逆时针方向(角度为 0 ∼ π 0\sim \pi 0∼π);反之,若结果为负,则表示向量 b ⃗ \vec b b 在向量 a ⃗ \vec a a 的顺时针方向。
怎么用这个性质:假设现在有三个点 v 1 v_1 v1, v 2 v_2 v2, v 3 v_3 v3,不妨设 a ⃗ = v 1 v 2 → , b ⃗ = v 1 v 3 → \vec a=\overrightarrow{v_1v_2}, \vec b=\overrightarrow{v_1v_3} a =v1v2 ,b =v1v3 。这时 a ⃗ \vec a a 就代表了 v 1 v_1 v1, v 2 v_2 v2 组成的直线的方向向量,若 b ⃗ \vec b b 在 a ⃗ \vec a a 的逆时针方向,就说明 v 3 v_3 v3 在直线左边,同理若 b ⃗ \vec b b 在 a ⃗ \vec a a 的顺时针方向,就说明 v 3 v_3 v3 在直线右边(若等于 0 ,说明三点共线)。
接下来就好办了,枚举 v 1 v_1 v1, v 2 v_2 v2(即枚举直线上两点),然后枚举 v 3 v_3 v3,通过向量叉积判断直线两侧点的个数,统计答案即可。
代码
python
import sys
def main():
data = sys.stdin.read().strip().split()
if not data:
return
it = iter(data)
n = int(next(it))
pts = []
for _ in range(n):
x = int(next(it))
y = int(next(it))
pts.append((x, y))
ans = 0
for i in range(n):
x1, y1 = pts[i]
for j in range(i+1, n):
x2, y2 = pts[j]
left = 0
right = 0
for k in range(n):
if k == i or k == j:
continue
xk, yk = pts[k]
# cross product (p2-p1) x (pk-p1)
cross = (x2 - x1) * (yk - y1) - (y2 - y1) * (xk - x1)
if cross > 0:
left += 1
elif cross < 0:
right += 1
# cross == 0 -> on the line, ignored
if left == right:
ans += 1
print(ans)
if __name__ == '__main__':
main()
小红的网格图构造2.0
思路
朴素想法是给所有 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j) 其中 i ≥ 0 , j ≥ 0 , 2 ∗ i < n , 2 ∗ j < m i\ge 0,j\ge0,2*i\lt n,2*j\lt m i≥0,j≥0,2∗i<n,2∗j<m,填上 1 1 1,其余位置补 0 0 0,就能满足题目任意 2 × 2 2\times 2 2×2 的区域包含 1 1 1 的约束。
这种构造方式显然是成立的,因为在任意行上,1 之间都只相差一格,间隔一定放不下长为 2 的窗口,因此长为 2 的窗口一定会包含一个 1,同理列。
这种构造方式也是最少的,因为如果移除任何一处1,或是拉长两个 1 之间的距离,就会导致有某个位置可以放下长为 2 的窗口,这意味着这个窗口内不包含 1。
这种构造方式的核心是相邻 1 间隔为 1。因此填充 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j), ( 2 ∗ i + 1 , 2 ∗ j ) (2*i+1,2*j) (2∗i+1,2∗j), ( 2 ∗ i , 2 ∗ j + 1 ) (2*i,2*j+1) (2∗i,2∗j+1), ( 2 ∗ i + 1 , 2 ∗ j + 1 ) (2*i+1,2*j+1) (2∗i+1,2∗j+1) 都是可以的。
问题在于 1 应该先填充在 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j), ( 2 ∗ i + 1 , 2 ∗ j ) (2*i+1,2*j) (2∗i+1,2∗j), ( 2 ∗ i , 2 ∗ j + 1 ) (2*i,2*j+1) (2∗i,2∗j+1), ( 2 ∗ i + 1 , 2 ∗ j + 1 ) (2*i+1,2*j+1) (2∗i+1,2∗j+1) 的哪一种位置上。
不难想到如果 1 1 1 的个数 k k k 如果很小,就填充在 ( 2 ∗ i + 1 , 2 ∗ j + 1 ) (2*i+1,2*j+1) (2∗i+1,2∗j+1) 上,如果 k k k 小到连这些位置都填不下,就说明无解。如果够多,就可以填充在 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j)。
靠外,在边界上就容易被 "卡" 没,总数就少。反之靠内,就不容易被 "卡" 没,总数就多。
如果 k k k 继续增大,比 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j) 位置个数要多,那么显然可以填到 ( 2 ∗ i + 1 , 2 ∗ j ) (2*i+1,2*j) (2∗i+1,2∗j), ( 2 ∗ i , 2 ∗ j + 1 ) (2*i,2*j+1) (2∗i,2∗j+1)。但是当填到 ( 2 ∗ i + 1 , 2 ∗ j + 1 ) (2*i+1,2*j+1) (2∗i+1,2∗j+1) 的位置上时, 0 0 0 就无法满足约束了,因此此时就无解了。
其实到这里不难发现, 0 0 0 和 1 1 1 角色是类似的,我们不关心是在 1 1 1 的网格上填充 0 0 0,还是在 0 0 0 的网格上填充 1 1 1。因此我们可以在 k ≥ n × m 2 k\ge \dfrac{n\times m}2 k≥2n×m 的时候在 0 0 0 的网格上填 1 1 1,反之在 k < n × m 2 k\lt \dfrac{n\times m}2 k<2n×m 的时候在 1 1 1 的网格上填 0 0 0。这样就不需要讨论当 k k k 比较小时的特判和填法,同时不管在哪个网格上填谁,我们可以复用同一种写法,具体实现见代码
代码
我们如果 k < n × m 2 k\lt \dfrac{n\times m}2 k<2n×m 就让 01 身份反转一下,解法就都是在 0 0 0 的网格上填充 1 1 1,用 flag 记录是否翻转,最后输出时根据 flag 判断 01 身份是否反转。
python
n, m, k = map(int, input().split())
if not (k >= (n//2)*(m//2) and n*m - k >= (n//2)*(m//2)):
print("No")
exit()
else:
print("Yes")
grid = [[0] * m for _ in range(n)]
flag = False
if k < n*m/2:
flag = True
k = n*m - k
cnt = 0
for i in range((n + 1) // 2):
for j in range((m + 1) // 2):
if cnt >= k:
break
grid[2 * i][2 * j] = 1
cnt += 1
if cnt >= k:
break
if cnt < k:
for i in range(n // 2):
for j in range((m + 1) // 2):
if cnt >= k:
break
grid[2 * i + 1][2 * j] = 1
cnt += 1
if cnt >= k:
break
if cnt < k:
for i in range((n + 1) // 2):
for j in range(m // 2):
if cnt >= k:
break
grid[2 * i][2 * j + 1] = 1
cnt += 1
if cnt >= k:
break
if flag:
for i in range(n):
for j in range(m):
grid[i][j] = 1 - grid[i][j]
for row in grid:
print("".join(map(str, row)))
G 小红的网格图构造1.0
思路
这题看着像 F,但是约束完全变了,所以是完全不同的两道题。
朴素想法是类似的,给所有 ( 2 ∗ i , 2 ∗ j ) (2*i,2*j) (2∗i,2∗j) 其中 i ≥ 0 , j ≥ 0 , 2 ∗ i < n , 2 ∗ j < m i\ge 0,j\ge0,2*i\lt n,2*j\lt m i≥0,j≥0,2∗i<n,2∗j<m,填上 1 1 1,其余位置补 0 0 0,就能满足题目任意 2 × 2 2\times 2 2×2 的区域包含 1 1 1 的这条约束。
问题在于如何构造连通块数量 = k =k =k。
经过上面的构造,此时连通块数量等于 1 的数量,即 ⌊ n 2 ⌋ × ⌊ m 2 ⌋ \left\lfloor\dfrac n2\right\rfloor\times \left\lfloor\dfrac m2\right\rfloor ⌊2n⌋×⌊2m⌋ 个连通块,我们通过把相邻 1 之间的 0 填充成 1(即填充 ( 2 ∗ i + 1 , 2 ∗ j ) (2*i+1,2*j) (2∗i+1,2∗j), ( 2 ∗ i , 2 ∗ j + 1 ) (2*i,2*j+1) (2∗i,2∗j+1)),就可以减少一个连通块,最终把所有 1 都串联起来,最少是剩下 1 个连通块。
增加连通块也好办,就是填充 ( 2 ∗ i + 1 , 2 ∗ j + 1 ) (2*i+1,2*j+1) (2∗i+1,2∗j+1),让相邻行的 1 错开。最多能填充到 ⌈ n m 2 ⌉ \left\lceil\dfrac {nm}2\right\rceil ⌈2nm⌉ 个连通块。
所以代码就很好写了,不过需要注意 n m 可能是 1,此时不存在 2 ∗ 2 2*2 2∗2 的网格,因此依然可以满足题目任意 2 × 2 2\times 2 2×2 的区域包含 1 1 1 的这条约束。这时即使 k=0 也可以构造,所以此时需要特判。
代码
python
import math
def main():
try:
n, m, k = map(int, input().split())
except ValueError:
return
if k == 0 and (n == 1 or m == 1):
print("Yes")
for _ in range(n):
print("0" * m)
return
elif k > 0 and k <= (n * m + 1) // 2:
print("Yes")
else:
print("No")
return
grid = [[0] * m for _ in range(n)]
if k <= ((n+1)//2)*((m+1)//2):
cnt = 0
for i in range((n+1)//2):
for j in range((m+1)//2):
grid[2*i][2*j] = 1
cnt += 1
for i in range((n+1)//2):
for j in range((m+1)//2-1):
if cnt > k:
grid[2*i][2*j+1] = 1
cnt -= 1
for i in range((n+1)//2-1):
if cnt > k:
grid[2*i+1][2*j] = 1
cnt -= 1
else:
cnt = 0
for i in range((n+1)//2):
for j in range((m+1)//2):
grid[2*i][2*j] = 1
cnt += 1
for i in range(n//2):
for j in range(m//2):
if 2*i+1 < n and 2*j+1 < m and cnt < k:
grid[2*i+1][2*j+1] = 1
cnt += 1
print("\n".join("".join(map(str, row)) for row in grid))
if __name__ == "__main__":
main()