背景
2017 年 1 月,我人生第二次来到东京,用的是旅游签,去参加东京大学创造情报学的入学考试。
创造情报学这个专业比较特殊的一点是他不考数学,只考实机编程,所以在准备考试的期间,我做了不少过往的题目,其中不少有意思的出题(比单纯刷 Leetcode 有意思得多)。
10 年后 AI 横空出世,对付这些题,常见的模型可能都不用跑 10 分钟。但是因为有当年的手写代码记录,非常适合用来给我重新上一上编程课。
于是我有请 Claude(Opus 4.7) 和 Codex(GPT-5.5)两位老师出场。
“再读一次题目”
题目大致是这样的:有向边随时间不断加入(或删除),构成一个动态有向图 $G(t)$。题目把"从固定根顶点 $v_0$ 出发可达的所有顶点集合"定义为根集合 $R(t)$,要求追踪它的大小变化,比如找出 $|R(t)|$ 首次突破 1000 的时刻。(东大创造情报学 2010 年冬)
查看完整题目
考虑一个顶点和有向边随时间增减的有向图。
设时刻 $t$ 的有向图为
$$ G(t)=(V(t),A(t)), $$其中,$V(t)$ 和 $A(t)$ 分别表示时刻 $t$ 的顶点集合和有向边集合。此外,记从顶点 $v_x$ 指向顶点 $v_y$ 的有向边为 $(v_x,v_y)$。
时刻 $t=0$ 时,有向图的初始状态定义为
$$ G(0)=(V(0),A(0)), $$其中,
$$ V(0)=\{v_0\},\qquad A(0)=\varnothing. $$进一步地,定义时刻 $t$ 时,从顶点 $v_0$ 出发可达的所有顶点所构成的集合为根集合(root set)$R(t)$。
问 1
考虑一个顶点和有向边不断增加的有向图。
定义一种针对有向图的操作 Add-VA 如下。给定两个顶点 $v_x$、$v_y$(按此顺序给出)时,Add-VA 对 $G(t-1)$ 执行如下操作,得到 $G(t)$。¹
$$ V(t)=V(t-1)\cup\{v_x,v_y\} \tag{1} $$$$ A(t)=A(t-1)\cup\{(v_x,v_y)\} \tag{2} $$时刻 $t$ 关于顶点 $v_x$、$v_y$ 的 Add-VA 操作,在文本文件的第 $t$ 行中按如下格式记述:
1x->y其中,$x$、$y$ 为 $0$ 至 $10000$ 的整数,分别对应于顶点 $v_0$ 至 $v_{10000}$。
下一页给出了一个示例,可供参考。
设将文本文件 a.txt 中记载的所有操作依次应用于 $G(0)$ 后得到的有向图为
回答下列问题。
1-1
求有向图 $G_a$ 的顶点数 $|V_a|$。
1-2
在有向图 $G_a$ 的所有顶点中,求出一个出度最大的顶点,并给出其出度。同样地,求出一个入度最大的顶点,并给出其入度。²
1-3
求满足
$$ |V(t_v-1)|<1000,\qquad |V(t_v)|\ge 1000 $$的时刻 $t_v$。
同样地,求满足
$$ |R(t_r-1)|<1000,\qquad |R(t_r)|\ge 1000 $$的时刻 $t_r$。
1-4
求顶点 $v_0$ 首次成为某个有向环一部分的时刻。
注¹ 式 (1)、式 (2) 的含义如下。
- 式 (1):若顶点 $v_x$ 不属于顶点集合 $V(t-1)$,则将 $v_x$ 加入 $V(t-1)$,得到 $V(t)$;对于顶点 $v_y$ 亦同。
- 式 (2):若从顶点 $v_x$ 指向顶点 $v_y$ 的有向边 $(v_x,v_y)$ 不属于有向边集合 $A(t-1)$,则将该有向边加入 $A(t-1)$,得到 $A(t)$。
注² 顶点 $v$ 的出度是指从顶点 $v$ 发出的有向边数;顶点 $v$ 的入度是指指向顶点 $v$ 的有向边数。
问 1 示例
设图 1 所示内容存储于文件中。表 1 给出了将该文件中的操作依次应用后,在各时刻 $t$ 的图 $G(t)$ 的顶点集合 $V(t)$、有向边集合 $A(t)$ 以及根集合 $R(t)$。
在时刻 $t=5$ 时,顶点数 $|V(5)|$ 为 $6$,有向边集合的大小 $|A(5)|$ 为 $5$,根集合的大小 $|R(5)|$ 为 $5$。
10->1
22->3
33->4
43->5
51->3图 1 问 1 的输入示例
表 1 问题 1 输入示例的执行结果
| $t$ | $V(t)$ | $A(t)$ | $R(t)$ |
|---|---|---|---|
| 0 | $\{v_0\}$ | $\varnothing$ | $\{v_0\}$ |
| 1 | $\{v_0,v_1\}$ | $\{(v_0,v_1)\}$ | $\{v_0,v_1\}$ |
| 2 | $\{v_0,v_1,v_2,v_3\}$ | $\{(v_0,v_1),(v_2,v_3)\}$ | $\{v_0,v_1\}$ |
| 3 | $\{v_0,v_1,v_2,v_3,v_4\}$ | $\{(v_0,v_1),(v_2,v_3),(v_3,v_4)\}$ | $\{v_0,v_1\}$ |
| 4 | $\{v_0,v_1,v_2,v_3,v_4,v_5\}$ | $\{(v_0,v_1),(v_2,v_3),(v_3,v_4),(v_3,v_5)\}$ | $\{v_0,v_1\}$ |
| 5 | $\{v_0,v_1,v_2,v_3,v_4,v_5\}$ | $\{(v_0,v_1),(v_2,v_3),(v_3,v_4),(v_3,v_5),(v_1,v_3)\}$ | $\{v_0,v_1,v_3,v_4,v_5\}$ |
问 2
考虑不仅进行有向边的添加,而且也进行有向边删除的情形。
在操作 Add-VA 之外,定义操作 Del-A 如下。给定有向边 $(v_x,v_y)$ 时,Del-A 对 $G(t-1)$ 执行如下操作,得到 $G(t)$。³
$$ A(t)=A(t-1)\setminus\{(v_x,v_y)\} \tag{3} $$时刻 $t$ 对有向边 $(v_x,v_y)$ 的操作 Del-A,与 Add-VA 同样,在文本文件的第 $t$ 行中按如下格式记述:
1!x->y设将文本文件 b.txt 中记载的所有操作依次应用于 $G(0)$ 后得到的有向图为
并设其根集合为 $R_b$。回答下列问题。
2-1
求有向图 $G_b$ 的有向边数 $|A_b|$。
2-2
关于有向图 $G_b$,求根集合的大小 $|R_b|$。
2-3
关于根集合的大小 $|R(t)|$,求所有满足
$$ |R(t-1)|<1000,\qquad |R(t)|\ge 1000 $$的时刻 $t$。
Python2 写的代码先适配成 Python3,重新跑数据。
中途 Claude 那边卡了一段时间(thinking 模式下偶尔会这样),我就把结果核验让 Codex 接手。我们吭哧吭哧地改了算法,拿新写的 DFS 算了一通,结果跑出来有结果了。
我不太确定,又顺手把同一道题丢给 Claude"再验算一下" ——翻车了。
我自己读题时根本没看——更准确说,10 年前的我也没好好看题。看示例就往以前做过的图论题上套。
我:好,那么 2-2 他说把 b.txt 的所有操作对 G0(我知道是图)进行了以后得到了一个完整图 Gb 路径合集 Rb 求 Rb 的大小
我:那么我从哪一个点出发?跟他的题目有什么关系?
起点不是你选的,起点是 R 的定义本身写死的。再读一次题目的 R(t) 定义:
Let R(t) be a root-set: the set of vertices consisting of the vertex v_0 and all reachable vertices from v_0 at time t.
注意这里有个关键细节:Claude 没有顺着我已经做的代码答,它直接重新引用了题目原文,告诉我"起点不是你可以选的,是 R 的定义里写死的"。
这是一个 meta 层的纠正——不是回答"从哪出发",是质疑"你为什么认为可以选起点"。
而我一直都用"输入第一条边的源顶点"作为起点。显然,Codex 也没有提醒过我起点本身就有问题。
problem fidelity(题意忠诚)
起点假设只是这道题的第一个陷阱。后面还有一层更深的术语陷阱:题目里的 R(t) 是 root-set(从根顶点 v_0 长出来的可达顶点集合),但中文/英文混着读,我一度把 R 自动联想成 “route”,把 |R(t)| 误解为"路径总数",进而把 Q2-3 的"|R(t)| 在 1000 上下穿越的所有时刻"想成"路径数穿越 1000",整个算法思路被带偏。
Claude 非常坚持不懈地在术语陷阱里反复纠正我:我每次试图确认一个错误理解时反复引用原定义把我拉回"R 是顶点集合,不是路径计数"。
这种持续锚定到原文(强硬?)的行为,跟前面那个例子是同一种对齐倾向的延伸——
problem fidelity (一种持续拒绝否定或忽视"题目定义"的能力)。
prompt fidelity(Prompt 忠诚)
first_source 错了。
first_source() 就是错的,不是实现细节问题,而是题意入口点错了。我现在把 v2 改成显式默认从 “0” 出发,并去掉"第一条边起点"这个假设。
Codex 之前接受了我的误读 prompt,得到了一个前提错了、但内部自洽的答案。
数值上这个答案跟真实答案差一个数量级——Q2-2 的 |R_b| 用 first_source 起点算出来是 7 或 1,正确答案是 357。只看程序输出,确实看不出哪里错。
更糟的是题目本身给的两个示例第一行都恰好是 0->1,所以 first_source() 在示例数据上巧合等于 "0",输出全对。
prompt fidelity vs problem fidelity —— 对齐目标的差异
最容易得出的浅结论是"Claude 更聪明"或者"Opus 4.7 完胜 GPT 5.5"。但这条结论的问题在于:
- 它没有解释为什么会有这种差异,只是把现象贴上一个模型优劣标签;
我更感兴趣的是背后的训练机制。两个模型展示的不是不同的智力,是不同的优先级:
Codex 倾向 prompt fidelity——以用户给的 prompt 为优先 ground truth。
你说 first_source 是起点,那 first_source 就是起点。你的 prompt 是合同,模型负责高质量地执行合同。
Claude 倾向 problem fidelity——以问题本身的定义为优先 ground truth,必要时质疑用户。
即使用户说起点是 first_source,题目原文里 R(t) 的定义把起点写死了,那我会先指出你和题目原文不一致,再看你决定怎么做。
这两种倾向都是有意为之的对齐选择,不是 bug。
OpenAI 的 coding 产品线(Codex CLI、GPT-5 系的 coding 角色)面向"执行力"场景——你已经知道要做什么,模型负责高质量地把它做出来。
Anthropic 的对齐里"必要时反向 push back 用户"的权重明显更高,在面对模糊 / 矛盾 / 可能误读的 input 时 Claude 倾向先停下来确认。
这不是谁比谁高明,是两家公司在 prompt 忠诚 vs 题意忠诚的 trade-off 上选择了不同的默认值。
两家公司在 prompt 忠诚 vs 题意忠诚上的 default 选择这件事,反映的是更深一层的产品哲学——执行器还是协作者,按合同做还是必要时挑战合同。
同样的 push back,在不同场景下是 feature 还是 bug
trade-off 能够成立,说明“高质量地执行合同”也一定有适合的场景。
在算法题、数学题、形式化任务里,push back 肯定更重要。
这类任务有一个客观的 problem definition——题目原文、数学公式、形式化规范——它存在于用户的 prompt 之外,且不会因为用户的误读而改变。
模型对照原始定义来质疑用户。
这次的图论可达性问题就是这种场景:题目原文就在那里,v_0 = "0" 写得清清楚楚。
但在现实业务的需求场景下,push back 不一定是 feature,甚至常常是 friction。
原因是这些场景里根本不存在一个客观的"题目原文"——业务需求是模糊的,用户给的 prompt 就是当下最权威的 spec。
一个总是质疑你 prompt 的模型,在这种场景下会反复打断节奏,用模型自己想象的"更合理"的需求来挑战你刚说出口的需求。
举个具体的例子:你跟 Claude 说"帮我把这个按钮改成红色",他可能回
“你确定要红色吗?根据可用性原则,警告色不应该用在主要 CTA 上”——这在算法题语境里是好习惯,在产品迭代语境里是 friction。
Codex 那种 prompt fidelity 强的执行调性反而更省事:你说红色,那就红色,下一个 task。
所以这次编程问题的的结论不能直接外推。“Claude 在所有场景都更好用”。
哪一个 default 更适合你,取决于你面对的问题——
定义是清晰/ 还是模糊?
它是在哪个对齐维度上完胜,那个维度跟我做的工作匹配吗
一个 workflow takeaway
这也给我一个好的启发,同时使用两个模型的好处:
当:**关键问题(spec 严格 / 错了代价大 / 自己不确定) cross-check **是更好实践。
一个模型作为执行者,另一个模型在最后阶段作为 reviewer——尤其当 reviewer 那一方对齐里有 push back 倾向时,价值最大。
多花一次 cross-check 的成本远低于一次 silent fail 的成本。
边界/诚实的结论
那么说到底,又不能证明什么呢?:
- 不构成评测。
- 模型版本一直在变。这次的具体表现差异下一个版本可能就反过来,或者两边都进化到同样的 push back 倾向。
- 算法题是高度结构化的场景,结论不能外推到所有 coding 任务。
我在 X 上常常看见"这一行 env 让 Codex 提速 3 倍 / Claude Code 隐藏功能"这样流量很高的文章——很多人看了就也不验证,直接点赞或者收藏,问题是它不标 scope,也不贴证据,不说自己结论的边界。
我不想写这样的暴论,所以诚实的结论是:
在一道有客观问题定义的算法题上,我误解了题目定义,Codex 接受了误读并按它执行,Claude 引用了题目原文质疑我的前提。 这是 prompt fidelity 与 problem fidelity 的对齐目标差异。这一差异在这个场景下倾向于让 Claude 占优。
花絮:入学考试怎么样了呢
很可惜,没有考上。
我的大学专业是软件工程,写代码对我来说就好像在组装一个虚空的积木,试错当然很枯燥,但却从来没有质疑过这个选择,因为最终成型都会让我感觉异常满足。
在我的职业生涯中也经常以为自己的天赋好像在这里,好像比别人更适合这个工作一些,可有时又常常在现实的“push back”中发觉,自己很可能也确实没有什么天赋,只是一个比别人更有耐心坐在电脑前面的普通人罢了。
Hi,我是 CheerChen。