CYaRon Is All You Need | 使用 CYaRon 制造数据
你还在被 Datagen 困扰吗?
需要一棵树、一张没有自环和重边的连通图、几组区间操作?还要让标程自动生成输出。放进同一套对拍?
让我们使用 CYaRon 吧!(肯定和 CYaRon! 没有关系!)
英明的你谷想到了这一点。你谷的 CYaRon 把这些环节放在了一组接口中,极大方便了数据生成和测试的流程!CYaRon 正是用来处理这些重复工作的!(当然,它不能替你写 std),但可以让你造结构、写文件、运行程序和比较输出连起来!
注:发挥大注意力注意到疑似没有完整介绍 CYaRon 的文章,作此篇,兼怀子由。
:::warning[开始前注意]{open}
- 你需要会 Python 的基础使用。
- 请通过
pip install cyaron提前安装西外诶而欧恩。 - 为了方便,大部分示例使用
from cyaron import *(虽然这样似乎不太像工程规范)。如无特殊说明都采用这种写法。 -
我们注意到 VectorRandomMode不在默认导出,所以被单独导入了。
Example
第一次使用时,先生成几组 .in/.out 文件。
这里提供一个示例。
from cyaron import *
point_counts = ati([0, 5, 20, 100])
edge_counts = ati([0, 4, 30, 300])
for case_id in range(1, 4):
io = IO(file_prefix="data", data_id=case_id)
n = point_counts[case_id]
m = edge_counts[case_id]
io.input_writeln(n, m)
graph = Graph.graph(
n,
m,
weight_limit=(1, 100),
self_loop=False,
repeated_edges=False,
)
io.input_writeln(graph.to_str(shuffle=True))
io.output_gen("std.exe")
好了,你已经会写数据器了!(会写 int main(){return 0;} 了!)
我们注意到:
IO管理输入、输出文件。Graph.graph()生成结构。input_writeln()写入输入。output_gen()运行 std 并生成输出。
ati() 会把可迭代对象逐项转成 int。数据规模写成 1e4、2e5 时,可以用它转回整数。
让我们 import
一般写一行即可。
from cyaron import *
它会导出这些:
Compare Edge Graph IO Merger
Polygon Sequence String Vector
:::info[注]
PyPI 版似乎没有 RangeQuery,但如果你查看 GitHub 源码会发现是有的。(这是为什么?)
:::
Python random 中常用的 choice、randint、randrange 和 uniform 也会一并导出。(你谷贴心的让你可以直接写 randint 什么的,不需要导入)不过 random 这个名字有点特殊:from cyaron import * 之后它指向的是 random 模块本身(被 cyaron.math 里同名的导入覆盖了),直接写 random() 会报 TypeError,得写 random.random()。
但是!But!这个需要单独导入!
from cyaron.vector import VectorRandomMode
:::info[其他]
如果你非常有兴趣去翻代码的话还会发现一个 visual.py 和 visualize(),不过并不能使用。
| 不知道是干什么的。 |
|---|
IO 管理文件和标程
让我们使用 IO 吧!
它可以非常方便的管理文件。图、序列和字符串最终通过它写进文件,标程生成输出和程序对拍也会使用它。
创建文件
让我们创建文件!
from cyaron import *
IO("a.in", "a.out")
IO(file_prefix="data")
IO(file_prefix="data", data_id=3)
IO("data{}.in", "data{}.out", data_id=3)
IO(file_prefix="hack/case", data_id=7, make_dirs=True)
IO(file_prefix="only_input", disable_output=True)
IO()
这些写法分别对应以下场景。
IO("a.in", "a.out")直接指定两个文件名。IO(file_prefix="data")生成data.in和data.out。- 带
data_id=3时,前缀模式会生成data3.in和data3.out。 - 文件名中的
{}会被data_id替换。 make_dirs=True会创建不存在的中间目录。disable_output=True只创建输入文件。- 不传参数时会创建临时文件,一般适合对拍。
可以使用 with 让文件在离开 with 块后关闭。
from cyaron import *
with IO(file_prefix="data", data_id=1) as io:
io.input_writeln(1, 2, 3)
写入内容
input_write()、input_writeln() 写输入文件,output_write()、output_writeln() 写输出文件。
带 ln 的函数会补换行,不带就不补。
from cyaron import *
io = IO("demo.in", "demo.out")
io.input_writeln(3, 5)
io.input_writeln([1, 4, 9])
io.input_write("hello")
io.input_write("world", separator=",")
io.input_writeln()
io.output_writeln("8")
:::info[输出] demo.in:
3 5
1 4 9
hello,world
demo.out:
8
:::
写入的默认分隔符是空格,列表和元组会递归展开(所以你可以注意到例子里列表的方括号没有写进文件)
需要其他分隔符时传入 separator。
from cyaron import *
with IO("comma.in", disable_output=True) as io:
io.input_writeln(
Sequence(lambda i, previous: i - 1).get(1, 10),
separator=",",
)
input_clear_content(pos=0) 和 output_clear_content(pos=0) 可以截断文件。flush_buffer() 会刷新输入缓冲区。程序需要立刻读取刚写入的内容时,这个方法能帮助排查缓冲问题。
Run std
from cyaron import *
with IO(file_prefix="sum", data_id=1) as io:
io.input_writeln(5)
io.input_writeln(1, 2, 3, 4, 5)
io.output_gen("std.exe")
output_gen() 会把当前输入文件交给标程,将标准输出写进对应的 .out 文件。
常用参数包括 shell_cmd、单位为秒的 time_limit(你可以让标程 replace_EOL。
输出已经关闭的 IO 对象不能调用 output_gen()。
Graph 生成图
图是 CYaRon 最常用的一部分。随机图、树、连通图和 DAG 都可以直接使用,手工构造则从 Graph 本身开始。
手工建图和输出
from cyaron import *
graph = Graph(4, directed=True)
graph.add_edge(1, 2, weight=7)
graph.add_edge(2, 4, weight=3)
graph.add_edge(1, 3, weight=9)
print(graph.edge_count())
print(graph)
:::info[输出]
3
1 2 7
1 3 9
2 4 3
:::
点默认从 1 开始编号。无向图调用一次 add_edge(1, 2) 就够了,内部会保存反向边,iterate_edges() 和字符串输出只会把这条无向边输出一次。
str(graph) 等价于 graph.to_str()。无权图可以换用 Edge.unweighted_edge。
from cyaron import *
graph = Graph.graph(5, 6, self_loop=False, repeated_edges=False)
print(graph.to_str(output=Edge.unweighted_edge))
:::info[输出] 注:图本身是随机生成的,每次输出都不一样,这里仅给出一种情况。
1 5
1 2
2 3
2 5
3 5
3 4
:::
to_str(shuffle=True) 会随机重映射点号,同时打乱输出,影响点号和边的输出顺序。可以让构造规律不出现在点号中。
邻接矩阵
from cyaron import *
graph = Graph(3, True)
graph.add_edge(1, 3, weight=3)
graph.add_edge(2, 3, weight=7)
graph.add_edge(2, 3, weight=4)
print(graph.to_matrix())
print(graph.to_matrix(default=0))
print(graph.to_matrix(default=0, merge=lambda old, edge: max(old, edge.weight)))
:::info[输出]
-1 -1 3
-1 -1 4
-1 -1 -1
0 0 3
0 0 4
0 0 0
0 0 3
0 0 7
0 0 0
:::
默认情况下,不存在的边记为 -1。多重边会以后一次写入的权值覆盖前一次。merge(old_value, edge) 能改成取最大值、最小值或其他规则。
树和森林
from cyaron import *
chain = Graph.chain(6, weight_limit=(1, 10))
flower = Graph.flower(6, weight_limit=10)
random_tree = Graph.tree(10)
mostly_chain = Graph.tree(10, chain=1)
mostly_flower = Graph.tree(10, flower=1)
mixed_tree = Graph.tree(10, chain=0.4, flower=0.3)
forest = Graph.forest(20, 4, weight_limit=10)
chain() 生成链,flower() 生成菊花图,tree() 用于随机树,forest(total_points, tree_count) 生成指定连通块数的森林。
Graph.tree() 的 chain 和 flower 都在 0 到 1 之间(试一试一起开会不会炸?)控制树形偏向,精确控制父亲时则使用 father_gen(cur)。
from cyaron import *
graph = Graph.tree(
10,
weight_gen=lambda: randint(100, 200),
father_gen=lambda current: max(1, current - randint(1, 3)),
)
二叉树由 binary_tree() 生成。
from cyaron import *
graph = Graph.binary_tree(15, left=0.35, right=0.35, weight_limit=(1, 5))
left 和 right 都必须位于 1。
一般图、连通图和 DAG
from cyaron import *
graph = Graph.graph(
8,
12,
directed=False,
self_loop=False,
repeated_edges=False,
weight_limit=(1, 20),
)
Graph.graph() 默认允许自环和重边。题目要求简单图时,务必显式写出 self_loop=False, repeated_edges=False。
无向连通图使用 UDAG()。
from cyaron import *
graph = Graph.UDAG(
10,
18,
self_loop=False,
repeated_edges=False,
weight_limit=100,
)
边数必须至少为 point_count - 1。UDAG() 仍默认允许自环,简单图同样要明确关闭自环和重边。
from cyaron import *
graph = Graph.DAG(
10,
20,
self_loop=False,
repeated_edges=False,
weight_limit=(1, 9),
)
DAG() 在 loop=False 时按点号约束方向,通常可以保证无环。将 loop=True 打开后,接口不再强制
Graph.connected() 只是统一入口。directed=False 时调用 UDAG(),directed=True 时调用 DAG()。
特殊图
作为万恶的出题或数据,想必是要让:
关于 SPFA 它死了。
CYaRon 贴心的为你提供了 hack_spfa()。
from cyaron import *
graph = Graph.hack_spfa(
30,
directed=True,
extra_edge=4,
weight_limit=(1, 100),
)
hack_spfa() 提供一份偏向卡 SPFA 的内置构造。虽然不一定能卡所有,但也可以让选手感到十分开心。
from_degree_sequence() 可以按度数序列生成图。
from cyaron import *
undirected = Graph.from_degree_sequence((2, 2, 1, 1, 1, 1))
directed = Graph.from_degree_sequence(((1, 2), (1, 1), (1, 0)))
无向图传入整数序列,有向图传入 (in_degree, out_degree) 序列。
self_loop、repeated_edges、weight_limit、weight_gen、n_iter 和 iter_limit 都可以进一步控制生成过程。
Vector 批量生成参数
Vector 适合生成点集、坐标、操作参数和随机数对。
from cyaron import *
points = Vector.random(5, [(1, 10), (1, 20)])
print(points)
position_range 中的一项代表一个维度。单个整数 x 表示 (left, right) 表示闭区间
Vector.random(5, [10])
Vector.random(5, [(1, 10)])
前者生成范围为 0 到 10 的一维数,后者从 1 到 10。
Vector.random() 有三种模式。
from cyaron import *
unique_points = Vector.random_unique_vector(5, [(1, 10), (1, 10)])
repeatable_points = Vector.random_repeatable_vector(5, [(1, 10), (1, 10)])
float_points = Vector.random_float_vector(5, [(0, 1), (0, 1)])
默认模式为 unique。唯一模式下,向量数量超过整个取值空间时会抛出 ValueError。
from cyaron import *
n = 8
m = 5
array = Vector.random(n, [(1, 100)])
operations = Vector.random_repeatable_vector(
m,
[(1, n), (1, n), (1, 100)],
)
这类写法比连续写多个 randint() 更容易检查维度和范围。
String 生成随机文本
from cyaron import *
lowercase = String.random(10)
custom = String.random(12, charset="abc")
mixed = String.random(20, charset=ALPHABET + NUMBERS)
默认字符集是 ALPHABET_SMALL。当 charset 是列表或元组时,String.random() 会从中选一个完整元素。(并不是字符哦!)
from cyaron import *
token = String.random(None, charset=["YES", "NO", "MAYBE"])
这会得到三个单词中的一个。
from cyaron import *
sentence = String.random_sentence(
8,
word_length_range=(2, 6),
first_letter_uppercase=True,
charset="abcde",
word_separators=" ",
sentence_terminators=".",
)
paragraph = " ".join(
String.random_sentence(
randint(6, 10),
charset=["aaa", "bbb", "ccc"],
sentence_terminators=".",
)
for _ in range(5)
)
random_sentence() 可以控制词数、词长、首字母、字符集、词间分隔符和句末符。random_paragraph() 与 word_count_range 一起使用目前会炸掉(PyPI 版和 GitHub 源码都一样),所以你可以注意到上面的写法比较诡异(并没有使用 word_count_range)。
满足格式约束的字符串还可通过正则生成。
from cyaron import *
identifier = String.random_regular(r"[0-9]+\w_.{0,9}", limit=5)
limit 控制各量词(如 +、{m,n})的最大重复次数,默认值为 10,它不是整串长度的上界。传入多个 pattern 时会随机选择其中一个。
Sequence 写递推数列
from cyaron import *
arithmetic = Sequence(lambda i, previous: 2 * i + 1)
print(arithmetic.get(1))
print(arithmetic.get(1, 5))
fibonacci = Sequence(
lambda i, previous: previous(i - 1) + previous(i - 2),
[0, 1],
)
print(fibonacci.get(2, 10))
formula(i, previous) 中的 i 是当前下标,previous 用于访问前面的项。初值可以是列表、元组或字典。
from cyaron import *
sequence = Sequence(lambda i, previous: previous(i - 1) + 3, {5: 100})
print(sequence.get(5))
print(sequence.get(6))
get(i) 取单项,get(left, right) 返回闭区间
Polygon 处理几何数据
from cyaron import *
polygon = Polygon([[0, 0], [1, 0], [1, 1], [0, 1]])
print(polygon.perimeter())
print(polygon.area())
显然这是一个正方形。
perimeter() 返回周长,area() 返回面积。
from cyaron import *
hull = Polygon.convex_hull(
20,
fx=lambda x: int(x * 1000),
fy=lambda y: int(y * 1000),
strict=True,
)
points = Vector.random_unique_vector(30, [(0, 1000), (0, 1000)])
simple = Polygon.simple_polygon(points)
convex_hull() 先在接近 fx、fy 映射到目标坐标。strict=True 会倾向避免共线边界点。simple_polygon() 用给定点集构造不自交的简单多边形。
Compare 对拍和比较输出
数据生成后,可以让 CYaRon 直接运行程序并比较结果。
比较现成输出
from cyaron import *
standard = IO("dummy.in", "std.out")
standard.output_writeln("114514")
Compare.output("another.out", std=standard)
std 可以是文件路径,也可以是 IO 对象。默认评测器是 DEFAULT_GRADER 的 NOIPStyle。
运行程序后比较
from cyaron import *
io = IO()
io.input_writeln(4)
io.input_writeln(1, 2, 3, 4)
Compare.program(
"my.exe",
input=io,
std_program="std.exe",
)
同一份输入会先后交给待测程序和标程,再由指定评测器比较输出。可以同时传入多个待测程序。
from cyaron import *
io = IO()
io.input_writeln("hello")
Compare.program(
"prog1.exe",
"prog2.exe",
"prog3.exe",
input=io,
std_program="std.exe",
)
NOIPStyle 会统一换行符,去掉每行行尾空白和结尾多余空行。FullText 对完整文本计算 SHA-256,任何字符差异都会判为不同。(除非你想体验恶心 NOIPStyle 吧)
from cyaron import *
io = IO()
io.input_writeln(1)
Compare.program(
"my.exe",
input=io,
std_program="std.exe",
grader="FullText",
)
待测程序可以写成 (command, timeout),超时单位为秒。
from cyaron import *
Compare.program(
(("python", "wa.py"), 1),
input=IO(),
std_program=("python", "std.py"),
)
Compare.output() 和 Compare.program() 都支持 max_workers、job_pool。默认 max_workers=-1 不会自动创建线程池。需要并行时,显式传 None 或非负整数。
Merger 拼接多张图
from cyaron import *
first = Graph.tree(5, weight_limit=10)
second = Graph.graph(4, 5, self_loop=False, repeated_edges=False, weight_limit=10)
merged = Merger(first, second)
merged.add_edge((0, 3), (1, 2), weight=114)
print(merged)
Merger(first, second, ...) 会重新编排各图的点号。add_edge((graph_index, vertex), (graph_index, vertex), weight=...) 用于跨图连边。底层大图保存在 merged.G,str(merged) 和 merged.to_str() 都能直接输出结果。
常量、数学函数和小工具
默认导出的常量包括以下内容。
PI E
ALPHABET_SMALL ALPHABET_CAPITAL ALPHABET NUMBERS
SENTENCE_SEPARATORS SENTENCE_TERMINATORS DEFAULT_GRADER
数论和组合相关函数包括 is_prime()、miller_rabin()、factor()、prime_sieve()、exgcd()、mod_inverse()、phi()、miu()、fibonacci()、binomial() 和 catalan_number()。
utils 中常见的小工具有 ati()、list_like()、int_like()、strtolines()、make_unicode()、process_args() 和 escape_path()(最后这个和 RangeQuery 一样只在 GitHub 源码里)。不再一一介绍。
process_args() 识别 --randseed=,可用固定种子复现数据。
from cyaron import *
process_args()
print(randint(1, 100))
python gen.py --randseed=114514
日志详细程度可由 log.set_quiet()、log.set_normal() 和 log.set_verbose() 控制。
模板
这里写一份模板(可以 Ctrl/Command-C-V)。你也可以写一份自己的模板以便使用。(或者让能工智人生成?)
from cyaron import *
def random_ranges(count, upper):
"""生成 count 个 [1, upper] 上的区间 (l, r),保证 l <= r。"""
ranges = []
for _ in range(count):
l = randint(1, upper)
ranges.append([l, randint(l, upper)])
return ranges
for case_id in range(1, 1001):
io = IO()
n = randint(1, 30)
q = randint(1, 30)
io.input_writeln(n, q)
io.input_writeln(Vector.random_repeatable_vector(n, [(1, 100)]))
for l, r in random_ranges(q, n):
io.input_writeln(l, r)
try:
Compare.program(
"my.exe",
input=io,
std_program="bf.exe",
grader="NOIPStyle",
)
except Exception:
# IO() 的临时文件会在进程退出时被删掉,先把失败输入拷出来
io.flush_buffer()
with open(io.input_filename, "r", encoding="utf-8") as fin, \
open(f"failed_{case_id}.in", "w", encoding="utf-8") as fout:
fout.write(fin.read())
print(f"Failed on case {case_id}")
print(f"Input saved at: failed_{case_id}.in")
raise
注意
Graph.graph()默认允许自环和重边。简单图必须明确写出self_loop=False, repeated_edges=False。Vector.random()的整数范围是[0, x] ,RangeQuery.random()的整数范围是[1, x] 。Graph.to_str(shuffle=True)会重映射点号。String.random()的charset为列表或元组时,返回一个完整元素。chain()和flower()的当前默认方向是无向的,但似乎没有人注意到这一点。
实战
让我们使用 CYaRon 来试一试吧!
实战?
随机生成发现几组都几乎一样?没有子任务限制?或者……
“根据最新消息,敌军摧毁了第 45 号、171 号据点,我军依然顽强抗争,修复了据点 37、98。现在可以反攻吗?”
“不可以,总司令。”
“敌军摧毁了第 33 号据点到 42 号据点的虫洞,现在可以反攻吗?”
“不可以,总司令。”
“我方修复了第 42 号据点,可以反攻吗?”
“不可以,总司令。”
“我方……”
“不可以,总司令。”
“同志,你只是一直在回答‘不可以’吗?你到底有没有好好判断形势?现在是关乎国家危亡的时刻……”
“总司令,您知道我不太聪明,没找到快速而正确地计算出结果的办法,但是据某项统计,我一直回答‘NO’的话,在一次战役中判断完全正确的概率是 45%。”
“那……”
“不可以,总司令。”
显然你需要先想清楚 Subtask,想清楚准备卡掉哪种假算法。到处 randint 已经把选手半夜笑醒了。
CYaRon 可以直接非常方便就把基础结构迅速造出来。 但是题目要求存在性、唯一性、合法操作序列这类性质,需要自己解决。
测试点
一般每个 Subtask 通常不只是把 if 完事的。
拿一道 DAG 题目举例,假设三档数据分别限制
| 测试点 | |||
|---|---|---|---|
| 1-5 | |||
| 6-10 | |||
| 11-20 |
十秒钟找出所有坑?
-
测试点 1 到 5 的权值上界是
300 ,全填500 显然不可以。 -
-
CYaRon 的简单 DAG 最多只有
\dfrac{n(n-1)}{2} 条不同边。
找出坑了?那写一下?
:::info[数据生成器代码]
from cyaron import *
for case_id in range(1, 21):
io = IO(file_prefix="dag_", data_id=case_id)
if case_id <= 5:
max_n, max_m, max_w = 1000, 2000, 300
elif case_id <= 10:
max_n, max_m, max_w = 20000, 50000, 500
else:
max_n, max_m, max_w = 10**6, 5 * 10**6, 500
# 这里就是你自己设计的了,这个示例是选择 1 号测最小规模,5、10、20 号测最大 n
if case_id == 1:
n = 1
elif case_id in (5, 10, 20):
n = max_n
else:
n = randint(500, max_n)
max_edges = n * (n - 1) // 2
upper_m = min(max_m, max_edges)
lower_m = n - 1
if lower_m > upper_m:
raise ValueError(f"case {case_id}: no feasible edge count for n={n}")
m = upper_m if case_id in (5, 10, 20) else randint(lower_m, upper_m)
if case_id in (5, 10, 20):
weight_gen = lambda: max_w
else:
weight_gen = lambda: randint(1, max_w)
graph = Graph.DAG(
n,
m,
weight_gen=weight_gen,
repeated_edges=False,
)
# 检查一下,当然你不检查也不是不行
assert 1 <= n <= max_n
assert lower_m <= m <= upper_m
io.input_writeln(n, m)
io.input_writeln(graph)
io.output_gen("std.exe")
:::
当然,除了满足题目要求还要注意一些小问题。
- 最小值和边界值。 需要给小规模、上限、关键参数留出测试点。随机显然是测不出来上界炸掉的边缘问题的。
- 结构性质。 子任务如果限制了树形、字符集、权值或操作类型,也是需要单独编写的。
- 结构形态差异。 同样规模的
n 和m ,既能生成链,也能生成星图、稀疏图、稠密图或随机树。如果选手口胡做法只在深链上爆,或者在稠密图里\textcolor{purple}{\text{Time Limit Exceeded}} ,光把规模调大不一定能卡掉。 - 分布。 无解与有解、答案为
0 与极大值、可行解是否唯一?需要你考虑一下。
顺序
如果有多种性质,也是有解决顺序要求的。(例如先选
区间操作先选左端点,再选不小于它的右端点,保证
具体的注意事项不再一一列举了(不然也写不完……)不过需要注意的是怎样从答案性质反推构造,避免整份测试数据都被 NO 或 -1 占满。(依旧不可以,总司令!)
实际使用
既然这样,那来试一试吧!(以下例子均适用于 Microsoft Windows,所以使用 std.exe 作为示例标准程序名称,你需要自行准备 std 程序。)
::::info[P8819 [CSP-S 2022] 星战]{open} 可查看题目。
你会注意到这一题就是著名的“不可以总司令”原题!
- 单边操作里,删边只能挑尚存的活边,修边必须选已损坏的边。
- 据点级操作批量翻转目标节点的所有入边状态。
把活着和被毁的边分别放进 EdgePool 来高效率随机取边,也能按值删边。
随机图的边数如果不等于
即便 YES 了!
所以你会注意到 不可以,总司令! 在这个数据下骗不到多少分。
from cyaron import *
class EdgePool:
"""支持 O(1) 随机取边、按值删边的动态边集"""
def __init__(self):
self.items = []
self.pos = {}
def __len__(self):
return len(self.items)
def __contains__(self, edge):
return edge in self.pos
def add(self, edge):
if edge not in self.pos:
self.pos[edge] = len(self.items)
self.items.append(edge)
def discard(self, edge):
index = self.pos.pop(edge)
last = self.items.pop()
if index < len(self.items):
self.items[index] = last
self.pos[last] = index
def random(self):
return choice(self.items)
for id in range(1, 21):
io = IO(file_prefix="galaxy_", data_id=id)
if id == 1:
n = 5
q = randint(2, 50)
allow_t2 = allow_t4 = True
elif id <= 3:
n = randint(2, 10)
q = randint(2, 50)
allow_t2 = allow_t4 = True
elif id <= 8:
n = randint(2, 10**3)
q = randint(2, 10**3)
allow_t2 = allow_t4 = True
elif id <= 10:
n = randint(2, 5 * 10**5)
q = randint(2, 5 * 10**5)
allow_t2 = allow_t4 = False # 无 t = 2 和 t = 4
elif id <= 12:
n = randint(2, 5 * 10**5)
q = randint(2, 5 * 10**5)
allow_t2, allow_t4 = True, False # 无 t = 4
elif id <= 16:
n = randint(2, 10**5)
q = randint(2, 5 * 10**5)
allow_t2 = allow_t4 = True
else:
n = randint(2, 5 * 10**5)
q = randint(2, 5 * 10**5)
allow_t2 = allow_t4 = True
# 初始图取 m = n 的函数图:每个点恰好一条出边(无自环),
# 所有出度天生为 1,恰好就是“可以反攻”的状态。
# id = 1 固定为一个完整的单环。
if id == 1:
edges = [(u, u % n + 1) for u in range(1, n + 1)]
else:
edges = []
for u in range(1, n + 1):
v = randint(1, n - 1) # 随机排除自环
if v >= u:
v += 1
edges.append((u, v))
m = n
io.input_writeln(n, m)
in_edges = [[] for _ in range(n + 1)]
for u, v in edges:
in_edges[v].append((u, v))
io.input_writeln(u, v)
io.input_writeln(q)
alive = EdgePool() # 当前可用的虫洞
destroyed = EdgePool() # 当前被摧毁的虫洞
for edge in edges:
alive.add(edge)
step = 0
used_t2_ever = False
while step < q:
remain = q - step
k_max = min(4, remain // 2)
if k_max >= 1:
# 规划一个“先破坏一小组边、再原样逐条修回”的片段:
# 修完的瞬间状态回到初始函数图,出度全为 1,答案就是 YES;
# 破坏阶段与修复中途的答案则是 NO。
k_target = randint(1, k_max)
plan = []
cost = 0
locked = set() # 已被据点级摧毁的终点,不能再单独删它的入边
picked = set()
tries = 0
while len(plan) < k_target and tries < 64:
tries += 1
need_v = allow_t2 and not used_t2_ever and \
not any(p[0] == 'v' for p in plan)
if allow_t2 and (need_v or randint(0, 2) == 0):
u = alive.random()[1]
extra = 2 if allow_t4 else 1 + len(in_edges[u])
if u in locked or len(in_edges[u]) == 0 or cost + extra > remain:
continue
plan.append(('v', u))
locked.add(u)
cost += extra
else:
e = alive.random()
if e in picked or e[1] in locked or cost + 2 > remain:
continue
plan.append(('e', e))
picked.add(e)
cost += 2
if not plan:
plan.append(('e', alive.random()))
for kind, val in plan: # 破坏
if kind == 'e':
u, v = val
alive.discard(val)
destroyed.add(val)
io.input_writeln(1, u, v)
else:
io.input_writeln(2, val)
for e2 in in_edges[val]:
if e2 in alive:
alive.discard(e2)
destroyed.add(e2)
step += 1
for kind, val in plan: # 先逐条修单边
if kind == 'e':
u, v = val
destroyed.discard(val)
alive.add(val)
io.input_writeln(3, u, v)
step += 1
for kind, val in plan: # 再修复据点级摧毁
if kind == 'v':
u = val
if allow_t4:
io.input_writeln(4, u)
step += 1
for e2 in in_edges[u]:
if e2 in destroyed:
destroyed.discard(e2)
alive.add(e2)
else:
for e2 in in_edges[u]:
if e2 in destroyed:
destroyed.discard(e2)
alive.add(e2)
io.input_writeln(3, e2[0], e2[1])
step += 1
used_t2_ever = used_t2_ever or any(p[0] == 'v' for p in plan)
else:
# 只剩一步:随便删一条边收尾
u, v = alive.random()
alive.discard((u, v))
destroyed.add((u, v))
io.input_writeln(1, u, v)
step += 1
io.output_gen("std.exe")
:::warning[注意]{open}
边的条数上界在无自环时是
| 如果使用随机边数需要考虑边界情况。 |
|---|
| :::: |
::::info[P14359 [CSP-J 2025] 异或和]{open} 可查看题目。
要求为给定长度为
如果数组完全随机生成,由于前缀异或和分散,能凑出异或和为
让我们构造边界!
- 把数组全部填成
k ,此时每个单点都是合法区间,答案直接达到理论上限n 。 - 填成
k,0,k,0,\dots ,当k\ne0 时,单点为k 的区间约占一半。这两种极端分布能迫使选区间的贪心逻辑跑满分支。最后一组大数据固定采用全k 构造,确保在最大规模下答案确实能冲到n ,避免全是规模极大而答案极小的同质数据。
:::info[想一想]{open}
第二种交替构造不能直接套到
第二种构造不能直接套到
from cyaron import *
def make_array(n, k, vmax, mode):
if mode == 0:
return [k] * n
if mode == 1:
if k == 0:
# [0, 0, ...] 的每个单点区间都合法
return [0] * n
return [k if i % 2 == 0 else 0 for i in range(n)]
return Vector.random_repeatable_vector(n, [(0, vmax)])
for id in range(1, 21):
io = IO(file_prefix="xor_", data_id=id)
if id == 1:
n, k, vmax = 2, 0, 1
a = [1] * n # 特殊性质 A
elif id == 2:
n, k, vmax = 10, randint(0, 1), 1
a = Vector.random_repeatable_vector(n, [(0, 1)])
elif id == 3:
n, k, vmax = 100, 0, 1
a = [1] * n # 特殊性质 A
elif id <= 5:
n, k, vmax = 100, randint(0, 1), 1
a = Vector.random_repeatable_vector(n, [(0, 1)])
elif id <= 8:
n, k, vmax = 100, randint(0, 255), 255
a = Vector.random_repeatable_vector(n, [(0, 255)])
elif id <= 10:
# 测试点 9, 10:k <= 255 且 a_i <= 255(特殊性质 C)
n, k, vmax = 1000, randint(0, 255), 255
a = make_array(n, k, vmax, randint(0, 2))
elif id <= 12:
n, k, vmax = 1000, randint(0, (1 << 20) - 1), (1 << 20) - 1
a = make_array(n, k, vmax, randint(0, 2))
elif id == 13:
n, k, vmax = 2 * 10**5, randint(0, 1), 1
a = Vector.random_repeatable_vector(n, [(0, 1)])
elif id <= 15:
n, k, vmax = 2 * 10**5, randint(0, 255), 255
a = make_array(n, k, vmax, randint(0, 2))
elif id == 16:
n, k, vmax = 2 * 10**5, randint(0, (1 << 20) - 1), (1 << 20) - 1
a = make_array(n, k, vmax, randint(0, 2))
elif id == 17:
n, k, vmax = 5 * 10**5, randint(0, 255), 255
a = make_array(n, k, vmax, randint(0, 2))
else:
n, k, vmax = 5 * 10**5, randint(0, (1 << 20) - 1), (1 << 20) - 1
a = make_array(n, k, vmax, 0 if id == 20 else randint(0, 2))
io.input_writeln(n, k)
io.input_writeln(a)
io.output_gen("std.exe")
:::warning[注意]{open}
测试点 9 与 10 属于特殊性质 C,限定
:::info[P9753 [CSP-S 2023] 消消乐]{open} 可查看题目。
给定一个小写字母串,求有多少个非空连续子串可以通过反复消除相邻相同字符变成空串。
这里显而易见不能随机生成字符串了(不然大部分情况根本没必要消除)。
让我们构造合法字符串!
- 空串本身可以消除。
- 在任何合法串里插入一对相同字符(示例为
c)如cc,消掉这对字符后剩下的部分依然能消除。
奇数长度的字符串显然绝不可能整体消除。(读者自证不难!)
我们可以在奇数长度时保留长度为
from cyaron import *
def reducible_string(n):
"""生成一个长度恰为偶数 n 的可消除串。"""
if n % 2:
raise ValueError("a reducible string must have even length")
pieces = []
for _ in range(n // 2):
c = choice("abc")
pieces.append(c + c)
return "".join(pieces)
for id in range(1, 21):
io = IO(file_prefix="game_", data_id=id)
if id <= 5:
n = randint(1, 10)
charset = None
elif id <= 7:
n = randint(1, 800)
charset = None
elif id <= 10:
n = randint(1, 8000)
charset = None
elif id <= 12:
n = randint(1, 2 * 10**5)
charset = None # 特殊性质 A:26 个字符独立等概率
elif id <= 14:
n = randint(1, 2 * 10**5)
charset = "ab" # 特殊性质 B
elif id <= 17:
n = randint(1, 2 * 10**5)
charset = None
elif id == 20:
n, charset = 2 * 10**6, None
else:
n = randint(10**4, 2 * 10**6)
charset = None
io.input_writeln(n)
# 1~5、15~19 号点以构造的可消除串为主,避免答案清一色是 0
if 6 <= id <= 14 or (id != 20 and randint(0, 3) == 0):
s = String.random(n, charset=charset) if charset else String.random(n)
else:
even_length = n if n % 2 == 0 else n - 1
s = reducible_string(even_length)
if n % 2:
# 奇数长度不可能整体消除,所以好心加一点
s += choice("abc")
io.input_writeln(s)
io.output_gen("std.exe")
:::
:::info[P14363 [CSP-S 2025] 谐音替换]{open} 可查看题目。
给出
特殊性质 B 要求串中只包含字符 a 和 b,且 b 恰好出现一次。所以我们直接用 'a' * pos + 'b' + 'a' * remaining 定位生成就可以了。
题目保证所有询问都满足
可以给 b,这样两串肯定不同了!
from cyaron import *
def special_string(length, b_pos=None):
"""特殊性质 B:仅含 a、b,且恰好一个 b。"""
if b_pos is None:
b_pos = randint(0, length - 1)
return 'a' * b_pos + 'b' + 'a' * (length - 1 - b_pos)
def lengths(count, char_limit, max_len, min_len=1):
"""返回 count 个长度,且两份等长字符串的总长度不超过 char_limit。"""
total = min(char_limit // 2, count * max_len)
if total < count * min_len:
raise ValueError("the length limit is too small")
base, extra = divmod(total, count)
result = [base] * count
for i in range(extra):
result[i] += 1
return result
def different_string(s):
replacement = 'b' if s[0] == 'a' else 'a'
return replacement + s[1:]
def embed_pair(craftable, length):
"""把某个替换对嵌进随机前后缀,返回一对等长的询问;放不下就返回 None。"""
for _ in range(64):
s1, s2 = choice(craftable)
if len(s1) <= length:
left = String.random(randint(0, length - len(s1)))
right = String.random(length - len(s1) - len(left))
return left + s1 + right, left + s2 + right
return None
for id in range(1, 21):
io = IO(file_prefix="replace_", data_id=id)
if id <= 2:
n, q, limit, special_b = 100, 100, 200, False
elif id <= 5:
n, q, limit, special_b = 1000, 1000, 2_000, False
elif id == 6:
n, q, limit, special_b = 1000, 1, 1_000_000, True
elif id <= 8:
n, q, limit, special_b = 10_000, 1, 1_000_000, False
elif id <= 10:
n, q, limit, special_b = 2 * 10**5, 2 * 10**5, 1_000_000, True
elif id <= 12:
n, q, limit, special_b = 2 * 10**5, 2 * 10**5, 2_000_000, False
elif id <= 14:
n, q, limit, special_b = 2 * 10**5, 1, 5_000_000, False
elif id <= 16:
n, q, limit, special_b = 2 * 10**5, 2 * 10**5, 5_000_000, True
else:
n, q, limit, special_b = 2 * 10**5, 2 * 10**5, 5_000_000, False
io.input_writeln(n, q)
pairs = []
for length in lengths(n, limit, max_len=20):
s1 = special_string(length) if special_b else String.random(length)
s2 = special_string(length) if special_b else String.random(length)
pairs.append((s1, s2))
io.input_writeln(s1, s2)
min_query_len = 2 if special_b else 1
craftable = [p for p in pairs if p[0] != p[1]]
for length in lengths(q, limit, max_len=20, min_len=min_query_len):
if special_b:
first_pos = randint(0, length - 1)
second_pos = (first_pos + randint(1, length - 1)) % length
t1 = special_string(length, first_pos)
t2 = special_string(length, second_pos)
elif q <= 8 or randint(0, 1) == 0:
# 纯随机的询问几乎必然无解(答案是 0),把替换对嵌进随机前后缀里,
# 就能构造出至少有 1 种方案(答案 >= 1)的询问。
built = embed_pair(craftable, length) if craftable else None
if built is None:
t1 = String.random(length)
t2 = different_string(t1)
else:
t1, t2 = built
else:
t1 = String.random(length)
t2 = different_string(t1)
io.input_writeln(t1, t2)
io.output_gen("std.exe")
:::
:::info[P2383 狗哥玩木棒]{open} 可查看题目。
题目给出若干根木棒的长度,询问能否将它们恰好用完,拼成一个正方形。
纯随机生成大概率无法拼成正方形,会产生大量 no,可以直接复现不可以总司令的荣光。
所以我们可以先确定正方形边长,再把四条边各自拆成若干小段,打乱后输出。显然可以保证至少有一种方式可以拼成正方形。
额外增加了几个边界测试点。
from cyaron import *
from random import shuffle
for id in range(1, 21):
io = IO(file_prefix="stick_", data_id=id)
T = randint(1, 10)
io.input_writeln(T)
for case_index in range(T):
m = randint(4, 20)
make_yes = case_index == 0 or (
case_index >= 2 and randint(0, 2) == 0
)
if make_yes:
counts = [1, 1, 1, 1]
for _ in range(m - 4):
counts[randint(0, 3)] += 1
side = randint(max(counts), 2000)
sticks = []
for count in counts:
remaining = side
for i in range(count - 1):
l = randint(1, remaining - (count - 1 - i))
sticks.append(l)
remaining -= l
sticks.append(remaining)
shuffle(sticks)
elif case_index == 1:
# 总长度不能被 4 整除,显然不可以啊喂
sticks = [1, 1, 1, 2]
else:
sticks = Vector.random_repeatable_vector(m, [(1, 2000)])
io.input_writeln(len(sticks), *sticks)
io.output_gen("std.exe")
:::
:::info[P1073 [NOIP 2009 提高组] 最优贸易]{open} 可查看题目。
给出包含有向边与无向边的混合图,要求从节点 1 走到节点
首先你需要确保 1 号点必须能到达
可以先随机一个目标差价
点数较小时可以枚举所有合法候选边洗牌选取,大数据可以逐条随机抽取并用 used_pairs 查重来增加效率(当然你不在意也可以全都枚举)。
from cyaron import *
from random import shuffle
for id in range(1, 21):
io = IO(file_prefix="trade_", data_id=id)
if id <= 5:
n = randint(2, 100)
max_m = min(200, n * (n - 1) // 2)
elif id <= 10:
n = randint(100, 1000)
max_m = min(5000, n * (n - 1) // 2)
else:
n = randint(10**4, 10**5)
max_m = min(5 * 10**5, n * (n - 1) // 2)
m = randint(n - 1, max_m)
profit = randint(0, 99)
low = randint(1, 100 - profit)
prices = Vector.random_repeatable_vector(n, [(low, low + profit)])
buy_pos = randint(0, n - 2)
sell_pos = randint(buy_pos + 1, n - 1)
prices[buy_pos][0] = low
prices[sell_pos][0] = low + profit
io.input_writeln(n, m)
io.input_writeln(prices)
# 保证 1 可达 n
edges = []
used_pairs = set()
for i in range(1, n):
edges.append((i, i + 1, randint(1, 2)))
used_pairs.add((i, i + 1))
if n <= 1000:
candidates = [
(u, v)
for u in range(1, n + 1)
for v in range(u + 1, n + 1)
if (u, v) not in used_pairs
]
shuffle(candidates)
for u, v in candidates[:m - (n - 1)]:
edges.append((u, v, randint(1, 2)))
else:
while len(edges) < m:
u, v = randint(1, n), randint(1, n)
if u == v:
continue
pair = (min(u, v), max(u, v))
if pair in used_pairs:
continue
used_pairs.add(pair)
edges.append((u, v, randint(1, 2)))
shuffle(edges)
for u, v, z in edges:
io.input_writeln(u, v, z)
io.output_gen("std.exe")
:::
:::info[P5658 [CSP-S 2019] 括号树]{open} 可查看题目。
一棵包含
需要注意树结构和括号匹配。
- 节点父亲必须满足
1\le f_i < i 才是题目规定的合法拓扑序。
括号序列也要分层设计:全左、全右括号各留一个测试点,其余用混合或分层构造,不然一堆测试点答案全是
这里可以维护未闭合左括号,在剩余长度不足时加右括号。
from cyaron import *
for id in range(1, 21):
io = IO(file_prefix="brackets_", data_id=id)
if id <= 2:
n, force_chain = randint(1, 8), True
elif id <= 4:
n, force_chain = randint(1, 200), True
elif id <= 7:
n, force_chain = randint(1, 2000), True
elif id <= 10:
n, force_chain = randint(1, 2000), False
elif id <= 14:
n, force_chain = randint(1, 10**5), True
elif id <= 16:
n, force_chain = randint(1, 10**5), False
elif id == 20:
n, force_chain = 5 * 10**5, False
else:
n, force_chain = randint(1, 5 * 10**5), False
io.input_writeln(n)
if id == 5:
mode = 0 # 全左括号,单独留一个测试点
elif id == 15:
mode = 1 # 全右括号,单独留一个测试点
elif id == 20:
mode = 3 # 分层构造
else:
mode = randint(2, 3)
if mode == 0:
s = String.random(n, charset="(")
elif mode == 1:
s = String.random(n, charset=")")
elif mode == 2:
s = String.random(n, charset="()")
else:
# 偶数长度时生成完整合法串,奇数长度时会余下一个左括号
stack_size = 0
chars = []
for pos in range(n):
remaining = n - pos
if stack_size and (stack_size >= remaining or randint(0, 1) == 0):
chars.append(')')
stack_size -= 1
else:
chars.append('(')
stack_size += 1
s = "".join(chars)
io.input_writeln(s)
# f_i 满足 1 <= f_i < i;指定测试点还要求 f_i = i - 1。
fathers = (
[i - 1 for i in range(2, n + 1)]
if force_chain
else [randint(1, i - 1) for i in range(2, n + 1)]
)
io.input_writeln(fathers)
io.output_gen("std.exe")
:::
:::info[P1025 [NOIP 2001 提高组] 数的划分]{open} 可查看题目。
将整数
显然这题并不会有神秘图论那么难造。
这个程序前三个测试点测极端情况,后面的则正常生成。
from cyaron import *
for id in range(1, 21):
io = IO(file_prefix="divide_", data_id=id)
if id == 1:
n, k = 7, 2 # 最小数据
elif id == 2:
n, k = 200, 6 # 最大数据
elif id == 3:
n, k = 200, 2 # 另一组边界
else:
n = randint(7, 200)
k = randint(2, 6)
io.input_writeln(n, k)
io.output_gen("std.exe")
:::
::::info[P1092 [NOIP 2004 提高组] 虫食算]{open}
这一题是研究最久的……
可查看题目。
在
随机数据似乎可以,但怎么确保有且只有一组解?(显然题解也只能找出解,不能说只有一种吧?)
显然很容易想到逐列回溯!
- 从低位到高位枚举还没赋值的字母,检查
(x+y+\mathrm{carry})\bmod n=z ,一旦搜出两种映射就停止。
但你会注意到 飞快(实则一分钟生成不出一组)。
不搜索呢?可惜的是能力所限,使用强制进位链,Distinct 和一串整数算术约束在大数据下还是炸……
最后发现了一个叫位向量编码的神器?
- 每个字母对应一个位向量,
Distinct保证它们取遍0\sim n-1 的范围。 - 竖式从最低位向最高位展开,每列都把新进位限制为
0 或1 。
先让求解器找一组解,再排除这组完整赋值,第二次判定无解才说明解唯一。这里引入 Z3 进行数学求解(微软开源还是太强大了),让 CYaRon 负责文件组织和随机抽样。
这个求解器可以核验这份随机结果是否严格符合题意。
:::info[备注]{open}
需要先安装 z3-solver。位向量适合交给 SAT 求解器处理。唯一的实例确认“不存在第二解”通常很快。
:::
当时迭代了几版本。
第一版,纯回溯。这个没什么好解释的。
(找不着当时的代码了,所以这个是 Kimi K3 根据信息重写的版本。)
class SearchBudgetExceeded(Exception):
pass
def count_mappings(a, b, c, n, limit=2, budget=10**5):
"""从低位到高位逐列回溯,统计 n 进制加法 a + b = c 的映射方案数。
数到 limit 就提前返回;搜索节点数超过 budget 就抛 SearchBudgetExceeded,
调用方直接把这种实例丢掉重摇,避免在病态实例上耗死。
"""
columns = [
(ord(a[i]) - 65, ord(b[i]) - 65, ord(c[i]) - 65)
for i in range(n)
]
assign = [-1] * n
used = [False] * n
total = 0
steps = 0
def dfs(col, carry):
nonlocal total, steps
if total >= limit:
return
if col < 0:
if carry == 0:
total += 1
return
x, y, z = columns[col]
letters = []
for letter in (x, y, z):
if assign[letter] == -1 and letter not in letters:
letters.append(letter)
def place(idx):
nonlocal total, steps
if total >= limit:
return
steps += 1
if steps > budget:
raise SearchBudgetExceeded
if idx == len(letters):
s = assign[x] + assign[y] + carry
if s % n == assign[z]:
dfs(col - 1, s // n)
return
letter = letters[idx]
for value in range(n):
if not used[value]:
assign[letter] = value
used[value] = True
place(idx + 1)
used[value] = False
assign[letter] = -1
place(0)
dfs(n - 1, 0)
return total
它在
| 15 | 16 | 17 | ||
|---|---|---|---|---|
| 唯一解 | 16 | 2 | 1 | 0 |
注:以上为 45 秒内统计。
所以严肃退役。
第二版,手工构造。搜不到就造!
比如想要链式进位,想让每列都进位
……严肃失败(有人有想法可以发在评论区)
第三版,Z3 整数编码。让我们使用求解器降维打击!
把字母建成整数变量,加 Distinct,进位用布尔变量。
直觉上机器搜索总该比手写回溯强……吧。
可惜并非,和手写速度比起来其实差不多,依旧慢死,严肃放弃。
var = {ch: z3.Int(ch) for ch in letters}
solver.add(z3.Distinct(*var.values()))
for v in var.values():
solver.add(v >= 0, v < n)
carry = z3.IntVal(0)
for i in range(n - 1, -1, -1):
s = var[a[i]] + var[b[i]] + carry
new_carry = z3.Int(f"c_{i}")
solver.add(z3.Or(new_carry == 0, new_carry == 1))
solver.add(s == var[c[i]] + n * new_carry) # 整数算术 + Distinct
carry = new_carry
solver.add(carry == 0)
整数 Distinct 加一串带进位的线性算术,Z3 通用算术推理,求解速度远不如布尔逻辑。
那怎么办呢?位向量!
第四版,位向量。把整数换成 z3.BitVec,进位判断从算术比较变成位运算,SAT 求解器对这种问题计算更快,速度大幅提升。
from cyaron import *
from random import shuffle
import z3
def unique_solution(a, b, c, n, timeout=15000):
"""用 Z3 位向量编码判定 n 进制虫食算 a + b = c 是否恰有 1 solution"""
letters = sorted(set(a + b + c))
if len(letters) < n:
return False
bw = (2 * n).bit_length() + 1# 容纳 2n-1 加进位
var = {ch: z3.BitVec(ch, bw) for ch in letters}
solver = z3.Solver()
solver.set("timeout", timeout)
solver.add(z3.Distinct(*var.values()))
for v in var.values():
solver.add(z3.ULE(v, n - 1))
carry = z3.BitVecVal(0, bw)
for i in range(n - 1, -1, -1):
s = var[a[i]] + var[b[i]] + carry
new_carry = z3.BitVec(f"c_{i}", bw)
solver.add(z3.Or(new_carry == 0, new_carry == 1))
solver.add(s == var[c[i]] + n * new_carry)
carry = new_carry
solver.add(carry == 0)
if solver.check() != z3.sat:
return False
model = solver.model()
solver.add(z3.Or(*[var[ch] != model[var[ch]] for ch in letters]))
return solver.check() == z3.unsat # 没有第二组解
def make_case(n, max_retries=300):
for _ in range(max_retries):
values = list(range(n))
shuffle(values)
letter_of = [0] * n
for letter, digit in enumerate(values):
letter_of[digit] = letter
addend1 = [randint(0, n - 1) for _ in range(n)]
addend2 = [randint(0, n - 1) for _ in range(n)]
total_digits = []
carry = 0
for i in range(n - 1, -1, -1):
s = addend1[i] + addend2[i] + carry
total_digits.append(s % n)
carry = s // n
if carry:
continue
total_digits.reverse()
a = "".join(chr(65 + letter_of[d]) for d in addend1)
b = "".join(chr(65 + letter_of[d]) for d in addend2)
c = "".join(chr(65 + letter_of[d]) for d in total_digits)
if unique_solution(a, b, c, n):
return a, b, c
raise RuntimeError(f"n={n}: {max_retries} 次尝试内没有造出唯一解")
for id in range(1, 21):
io = IO(file_prefix="alpha_", data_id=id)
if id <= 6:
n = randint(3, 10)
elif id <= 10:
n = randint(11, 15)
elif id <= 18:
n = randint(16, 20)
else:
n = randint(21, 24)# 这里也没有占满,但是更大即使 Z3 版本也不稳定了,你可以试试
a, b, c = make_case(n)
io.input_writeln(n)
io.input_writeln(a)
io.input_writeln(b)
io.input_writeln(c)
io.output_gen("std.exe")
注意到其中最大也只有
注:这个生成器没有排除“BACB + ACDC = DADA 的输入时构造 A=1, B=2, C=3, D=0(这里作为数据本身,所以没有使用 |
|---|
CYaRon 省下了写循环和拼图的时间,但很多地方还是得自己解决哦!
后记
这篇文章从今年六月就开始计划编写……但一直没多少时间。
前前后后拖了大概三个月才完成。也花费了不少时间。希望对你有帮助。
主要写作灵感来源于基于CYaRon的数据制造流程漫谈,但我主要是想对完整使用进行更详细的讲解。(更加小白一点?)
:::info[AI 使用说明]{open}
虫食算中的第一版代码使用 Kimi K3 重写。
部分 API 列表由 AI 辅助整理,但由人工编写。
| 保证智人贡献严格大于人工智能。 |
|---|
本文采用 Creative Commons CC0 1.0 Universal 发布,不提供任何担保。
在适用法律允许的最大范围内,本作品的全部著作权及相关权利为公共领域。