造题记录:出强制在线题,但是对树的边强制在线

今天造了一个数据结构题,具体题面是什么就不说了,题目名称是 sosomst(为将来埋下伏笔了)。输入格式是,第一行 \(n, typ\),接下来两行的点权,然后是一棵树。输出 \(n-1\) 行的数字,树边强制在线(对的,树边是强制在线)。以下是我生成这题数据的方法。

std.cpp

肯定是自己写了,但是先不要实现强制在线。将 std.cpp 编译为可执行文件 main。

online.py

可以运行完 std 跑出 answer 之后,再对 input 进行加密。这里写一个程序 online.py,使用 python online.py in ans 将 in 进行加密。用到 sys.argv,表示命令行参数,sys.argv[0] 总是这个程序的名字,以后就是参数。程序首先读取了 in,然后根据 ans 加密了 in。

import os, sys

assert(len(sys.argv) >= 3)

inf = sys.argv[1]
ans = sys.argv[2]

with open(inf, "r") as file:
    readLn = file.readline
    n, typ = map(int, readLn().split())
    A = map(int, readLn().split())
    B = map(int, readLn().split())
    op = []
    for i in range(1, n):
        x, y = map(int, readLn().split())
        op.append((x, y))

with open(inf, "w") as file:
    with open(ans, "r") as outf:
        readLn = outf.readline
        print(n, typ, file=file)
        print(" ".join(map(str, A)), file=file)
        print(" ".join(map(str, B)), file=file)
        z = 0
        for x, y in op:
            if typ == 0:
                print(x, y, file=file)
            else:
                print(x ^ z, y ^ z, file=file)
            z = int(readLn())

mker.py

生成输入文件,然后跑出 ans,再对输入加密。

可以用形如 " ".join([f"{rnd(1, v)}" for i in range(n)]) 快速生成一行 \(n\) 个随机数,用到了 python 的列表推导 [func(i) for i in range(n)],其中 func(i) 在这里是 f"{rnd(1, v)}" 相当于 str(rnd(1, v))。外面使用 str.join 方法,它接受一个 str 类型的列表,用分隔符连接,所以在生成随机数后马上将其转化为 str 类型。

print("\n".join([f"{per[rnd(0, i - 1)]} {per[i]}" for i in range(1, n)])) 这个就是随机了一棵树,每个点 \(i\) 随机 \([1, i-1]\) 中的一个点作为父亲,再随机打乱节点编号。生成一共 \(n-1\) 个 str,用换行链连接输出。

外部的 make 直接重写了 print 的默认输出文件,print 的文件可以通过 print(a, file=file) 重定向,默认是 file=sys.stdout。首先保存一份 sys.stdout,然后将 sys.stdout 赋值为 open("in", "w"),跑 makeIn,再将 sys.stdout 摁回去。然后进行 os.system 调用。

import sys, os, random
rnd = random.randint

def makeIn(n, typ, v = int(1e8), mode = 0):
    print(n, typ)
    print(" ".join([f"{rnd(1, v)}" for i in range(n)]))
    print(" ".join([f"{rnd(1, v)}" for i in range(n)]))
    if mode == 0:
        per = [i + 1 for i in range(n)]
        random.shuffle(per)
        print("\n".join([f"{per[rnd(0, i - 1)]} {per[i]}" for i in range(1, n)]))
    else:
        print("\n".join([f"{i} {i + 1}" for i in range(1, n)]))

def make(name, n, typ, v = int(1e8), mode = 0):
    name = "./data/sosomst" + name
    temp = sys.stdout
    with open(name + ".in", "w") as file:
        sys.stdout = file
        makeIn(n, typ, v, mode)
    sys.stdout = temp
    os.system(f"./main < {name}.in > {name}.ans")
    os.system(f"python3 online.py {name}.in {name}.ans")

造数据

使用 python 调出 python console,然后 from mker import * 将 mker.py 中的所有东西导入(import mker 的效果是 mker.make(),不好用)。然后直接在 console 中使用 make("0-0", n, 0, v = int(1e7), mode = -1) 之类的方法直接动态使用。

config.yml

编写 config.py,它应当对所有测试点,分好 subtask,调好对应的分数和时空限制。首先获取这些测试点的名字,使用 linux 下的 find(我是 windows.wsl 环境)找出名字,形如 find . -name 'sosomst{id}-?.in',这里的单引号里面是正则表达式(支持简单的 *?)。因为 os.system 的返回值不是那个命令的返回值,因此可能需要将 find 的输出重定向到文件然后再读取。

def getTest(id):
    os.system(f"find . -name 'sosomst{id}-?.in' > __temp__")
    with open("__temp__", "r") as file:
        content = str(file.read())
    return content.split()

然后枚举 subtask 编号和分数进行赋分。

with open("./data/config.yml", "w") as file:
    for sid, score in [(i, 10 + (10 if i in [4, 5] else 0)) for i in range(8)]:
        for name in map(lambda s: s[7:], getTest(sid)):
            print(name + ":", file=file)
            print(f"  timeLimit: 2000", file=file)
            print(f"  memoryLimit: 512000", file=file)
            print(f"  score: {score}", file=file)
            print(f"  subtaskId: {sid}", file=file)

上传 Luogu 即可。吐槽一下为什么 Luogu 要求压缩包 < 50M,解压后 < 100M,完全不够发挥啊!

mkt.py

我突然发现本题的树形态是重要的(?),于是需要加强数据。

normal

随机父亲。

print("\n".join([f"{rnd(1, i)} {i + 1}" for i in range(1, n)]))

chain

链是其中一个部分分。

print("\n".join([f"{i} {i + 1}" for i in range(1, n)]))

headtail

本题树边加入顺序是有影响的(如你所见,树边其实不是树边),针对本题有一种阴间卡法:一条链,但是加入树边的顺序是从头到尾和从尾到头两个指针随机交换。

可以考虑假的归并排序。

 # there will be some zero in the answer
        sml = [f"{i} {i + 1}" for i in range(1, n // 2)]
        big = [f"{i} {i + 1}" for i in range(n // 2, n)]
        big.reverse()
        i = 0
        j = 0
        out = []
        while i < len(sml) and j < len(big):
            if rnd(0, 1) == 0:
                out.append(sml[i])
                i += 1
            else:
                out.append(big[j])
                j += 1
        while i < len(sml):
            out.append(sml[i])
            i += 1
        while j < len(big):
            out.append(big[j])
            j += 1
        print("\n".join(out))

插曲:python gdb

写上面那玩意,一开始将 and 写成 or 了,然后报 RE,我 bingfs 发现可以对 python 程序进行 gdb 调试。gdb python3,然后 run <name>.py <args> 就行,相当于把 python3 当作可执行文件了,然后命令行传参给它。然后在 sml[i] RE 之后推出,看不了堆栈,比较烦。直接在那里 assert(i < len(sml)) 竟然 assertion failed,终于发现应该是 and。。。

binary

完全二叉树也是本题一个重要卡法。但要注意顺序,这里使用 list.reverse 方法,他竟然是个方法,返回值是 (),不是函数,一点不符合我对 python 的想象。

        out = [f"{(i + 1) // 2} {i + 1}" for i in range(1, n)]
        out.reverse()
        print("\n".join(out))

sqrt

考虑一个 \(\sqrt n\) 个节点的完全二叉树,但是每个节点上是 \(\sqrt n\) 长的链。

wait = []
        unit = int(math.sqrt(n))
        for l in range(1, n + 1, unit):
            r = min(l + unit, n + 1) # [l, r)
            for j in range(l, r - 1):
                print(f"{j} {j + 1}")
            wait.append(l)
        out = []
        for i in range(len(wait)):
            if i == 0: continue
            out.append(f"{wait[i]} {wait[i // 2]}")
        out.reverse()
        print("\n".join(out))

验题人

原数据范围是 3e5,他写了 \(O(n\log^2n)\),在 binary 的数据点跑了 998ms。非常好啊,于是我往里面塞了一组 5e5。于是就算是结束了。

posted @ 2023-10-15 20:42  caijianhong  阅读(23)  评论(0编辑  收藏  举报