extract_reference_attachments.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #!/usr/bin/env python3
  2. """Move standalone C++ solutions out of lessons and insert core pseudocode.
  3. This is an idempotent course-layout migration. It identifies the nearest
  4. preceding VJudge problem link for each fenced block containing main(), writes
  5. the block to 参考实现/<topic>/<OJ-id>.cpp, and replaces it with concise
  6. Python-like pseudocode plus a relative attachment link.
  7. """
  8. from __future__ import annotations
  9. import re
  10. from pathlib import Path
  11. PSEUDOCODE: dict[str, str] = {
  12. "POJ-2230": """for each road (u, v):
  13. add_arc(u, v); add_arc(v, u)
  14. stack = [1]; reversed_route = []
  15. while stack:
  16. u = stack[-1]
  17. if u still has an unused outgoing arc: stack.append(take_one_arc(u))
  18. else: reversed_route.append(stack.pop())
  19. output reverse(reversed_route)""",
  20. "HDU-1116": """for each word:
  21. add directed edge first_letter -> last_letter
  22. update in_degree, out_degree, and undirected DSU
  23. connected = all nonzero-degree letters have one DSU root
  24. degree_ok = all differences are 0, or exactly one +1 and one -1
  25. answer = connected and degree_ok""",
  26. "CodeForces-508D": """encode every length-2 string as a vertex
  27. for trigram xyz: add edge xy -> yz
  28. check directed Euler degree conditions and select start
  29. path = Hierholzer(start)
  30. if len(path) != edge_count + 1: print NO
  31. else: print first 2-char vertex, then append each later vertex's last char""",
  32. "CodeForces-723E": """answer = number of even-degree original vertices
  33. add dummy vertex 0 and edge 0-v for every odd-degree vertex v
  34. for every augmented connected component:
  35. follow an Euler circuit and orient each traversed original edge forward
  36. discard dummy edges and output recorded original orientations""",
  37. "CodeForces-427C": """components = Tarjan_SCC(graph)
  38. total_cost = 0; ways = 1
  39. for component in components:
  40. best = minimum(cost[v] for v in component)
  41. total_cost += best
  42. ways *= count(v in component where cost[v] == best) (mod MOD)""",
  43. "POJ-1236": """component = Tarjan_SCC(graph)
  44. if there is only one component: output 1, 0
  45. build condensation indegree and outdegree
  46. sources = count(indegree == 0)
  47. sinks = count(outdegree == 0)
  48. output sources, max(sources, sinks)""",
  49. "POJ-2186": """component = Tarjan_SCC(graph)
  50. compute every condensation component's size and outdegree
  51. sink_components = components with outdegree 0
  52. if len(sink_components) != 1: output 0
  53. else: output size of the unique sink component""",
  54. "CodeForces-1000E": """bridges = find_bridges_with_edge_ids(graph)
  55. component = flood_fill_while_ignoring(bridges)
  56. bridge_tree = contract_components_and_keep_bridges()
  57. a = farthest_vertex(bridge_tree, arbitrary_root)
  58. b, distance = farthest_vertex(bridge_tree, a)
  59. output distance""",
  60. "POJ-3177": """bridges = find_bridges_with_edge_ids(graph)
  61. component = flood_fill_while_ignoring(bridges)
  62. degree = degrees in the contracted bridge tree
  63. leaves = count(degree == 1)
  64. output ceil(leaves / 2)""",
  65. "CodeForces-732F": """find bridges and edge-biconnected components
  66. root = component with maximum number of original vertices
  67. inside each component: orient DFS-tree edges down and back edges up
  68. build the bridge tree rooted at root
  69. orient every bridge from child component toward parent component
  70. output size(root) and all orientations in input order""",
  71. "POJ-1144": """DFS(u, parent_edge):
  72. dfn[u] = low[u] = next_time()
  73. for each edge u-v except parent_edge:
  74. if v unvisited:
  75. DFS(v); low[u] = min(low[u], low[v])
  76. if u is non-root and low[v] >= dfn[u]: mark u as cut
  77. else: low[u] = min(low[u], dfn[v])
  78. root is cut iff it has at least two DFS children""",
  79. "CodeForces-962F": """during undirected Tarjan, push tree/back edge IDs on a stack
  80. when low[child] >= dfn[parent]:
  81. pop through the parent-child edge to obtain one vertex-biconnected block
  82. vertices = distinct endpoints of popped edges
  83. if number_of_edges == number_of_vertices:
  84. add every block edge to answer
  85. sort and output answer edge IDs""",
  86. "AtCoder-abc258_g": """neighbor[i] = bitset of vertices adjacent to i
  87. sum = 0
  88. for every edge i-j with i < j:
  89. sum += popcount(neighbor[i] AND neighbor[j])
  90. output sum / 3""",
  91. "CodeForces-1097F": """state[x][d] = parity of elements in multiset x divisible by d
  92. singleton(v): set exactly the divisor bits of v
  93. union(y, z): state[x] = state[y] XOR state[z]
  94. gcd_product(y, z): state[x] = state[y] AND state[z]
  95. query(v): parity = popcount(state[x] AND squarefree_multiple_mask[v]) mod 2""",
  96. "CodeForces-914F": """position[c] = bitset of indices currently containing c
  97. update(i, c): clear i in old character set; set i in c
  98. query(l, r, pattern):
  99. match = all_ones
  100. for offset, c in enumerate(pattern):
  101. match &= position[c] >> offset
  102. discard start positions outside [l, r-len(pattern)+1]
  103. return popcount(match)""",
  104. "POJ-3461": """pi = prefix_function(pattern)
  105. matched = answer = 0
  106. for c in text:
  107. while matched > 0 and c != pattern[matched]: matched = pi[matched-1]
  108. if c == pattern[matched]: matched += 1
  109. if matched == len(pattern):
  110. answer += 1; matched = pi[matched-1]""",
  111. "POJ-2406": """pi = prefix_function(s)
  112. period = len(s) - pi[-1]
  113. if len(s) is divisible by period: output len(s) / period
  114. else: output 1""",
  115. "CodeForces-432D": """pi = prefix_function(s)
  116. occ[pi[i]] += 1 for every position i
  117. for length from n down to 1: occ[pi[length-1]] += occ[length]
  118. occ[length] += 1 for each prefix's occurrence at position 0
  119. borders = n, pi[n-1], pi[pi[n-1]-1], ...
  120. output reversed(borders) with occ[length]""",
  121. "CodeForces-471D": """if target length == 1: output source length
  122. text = adjacent_differences(source_heights)
  123. pattern = adjacent_differences(target_heights)
  124. output KMP_occurrence_count(pattern, text)""",
  125. "POJ-3974": """d1 = odd_palindrome_radii_by_Manacher(s)
  126. d2 = even_palindrome_radii_by_Manacher(s)
  127. answer = max(max(2*d1[i]-1), max(2*d2[i]))
  128. output answer""",
  129. "CodeForces-17E": """d1, d2 = Manacher(s)
  130. P = sum(d1) + sum(d2)
  131. for each center and radius:
  132. range_add all represented start positions
  133. range_add all represented end positions
  134. start, end = prefix_sums(the two difference arrays)
  135. disjoint = sum(start[i] * number_of_palindromes_ending_before_i)
  136. output choose(P, 2) - disjoint (mod MOD)""",
  137. "AtCoder-abc340_d": """for each stage i:
  138. add edge i -> i+1 with cost A[i]
  139. add edge i -> X[i] with cost B[i]
  140. dist = Dijkstra(graph, source=1)
  141. output dist[N]""",
  142. "AtCoder-abc277_e": """state = (vertex, currently_passable_edge_label)
  143. for original edge with label a: connect its endpoints in layer a with cost 1
  144. for each switch vertex: connect its two layers with cost 0
  145. dist = zero_one_BFS(start=(1, label_1))
  146. output min(dist[(N,0)], dist[(N,1)])""",
  147. "CodeForces-449B": """train[v] = minimum train length to v
  148. run road-only Dijkstra with seeds (capital,0) and every finite train[v]
  149. mark v if some road u-v satisfies dist[u] + w == dist[v]
  150. needed = count(v where train[v] == dist[v] and v has no road predecessor)
  151. output total_trains - needed""",
  152. "CodeForces-59E": """BFS state is (previous_vertex, current_vertex); start = (0, 1)
  153. for neighbor next of current:
  154. skip if (previous, current, next) is forbidden
  155. visit state (current, next) and store parent state
  156. stop at first state whose current vertex is N
  157. follow parent states to restore the vertex path""",
  158. "CodeForces-229B": """run Dijkstra from planet 1
  159. when finalizing u at time t (unless u is destination):
  160. binary-search t in u's sorted blocked times
  161. advance depart across the consecutive blocked run t,t+1,...
  162. relax every edge using depart + edge_weight
  163. output destination distance or -1""",
  164. "CodeForces-545E": """run Dijkstra from source
  165. on shorter distance to v: replace distance and parent edge
  166. on equal distance: replace parent edge only if its own weight is smaller
  167. because all weights are positive, chosen parents strictly decrease distance
  168. output sum and IDs of one chosen parent edge for every non-source vertex""",
  169. "POJ-3255": """first[source] = 0; second[*] = infinity
  170. pop a distance label d at u
  171. for edge u-v:
  172. candidate = d + weight
  173. if candidate < first[v]: swap(candidate, first[v]); push first[v]
  174. if first[v] < candidate < second[v]: set and push second[v]
  175. output second[destination]""",
  176. "POJ-3169": """convert every constraint to x[v] <= x[u] + weight and add edge u->v
  177. add order edges (i+1)->i with weight 0
  178. if Bellman-Ford from a super-source finds a negative cycle: output -1
  179. dist = Bellman-Ford(source=1)
  180. if N is unreachable: output -2
  181. else: output dist[N], the tightest upper bound on x[N]-x[1]""",
  182. "AtCoder-arc076_b": """candidates = []
  183. sort towns by x; add every adjacent pair
  184. sort towns by y; add every adjacent pair
  185. give each candidate its true min(|dx|, |dy|) cost
  186. output Kruskal(candidates)""",
  187. "CodeForces-1245D": """treat building station i as edge super_source-i with cost c[i]
  188. initialize Prim key[i] = c[i], parent[i] = super_source
  189. repeat n times:
  190. select unused city v with minimum key
  191. record station if parent is source, otherwise record wire
  192. update every unused city's key by wire_cost(v, city)
  193. output total and recorded construction""",
  194. "CodeForces-1095F": """hub = vertex with minimum a[hub]
  195. candidates = all special offers
  196. for every v != hub: add ordinary edge hub-v with cost a[hub]+a[v]
  197. output Kruskal(candidates)""",
  198. "CodeForces-609E": """T, total = Kruskal_MST(graph)
  199. preprocess binary lifting for maximum edge on every tree path
  200. for edge e=(u,v,w):
  201. if e belongs to T: answer[e] = total
  202. else: answer[e] = total + w - maximum_on_tree_path(u,v)""",
  203. "CodeForces-160D": """sort edges into equal-weight groups; DSU contains only lighter edges
  204. for each group:
  205. edges already inside one DSU component are 'none'
  206. contract DSU roots and build a temporary multigraph from remaining edges
  207. temporary bridges are 'any'; other remaining edges are 'at least one'
  208. union all group edges in the global DSU""",
  209. "CodeForces-1706E": """process input edges by index; every successful DSU merge creates a new parent node
  210. new node value = current edge index; its children are the two old component roots
  211. preprocess LCA on the reconstruction tree
  212. need[i] = value(LCA(original_vertex_i, original_vertex_i+1))
  213. answer(l,r) = max(need[l .. r-1]), or 0 when l == r""",
  214. "CodeForces-888G": """sort and deduplicate values
  215. solve(group, bit):
  216. split group by current bit
  217. answer = solve(zero_side, bit-1) + solve(one_side, bit-1)
  218. if both sides exist:
  219. build a lower-bit binary trie for the smaller side
  220. answer += 2^bit + minimum lower-bit XOR across the split
  221. return answer""",
  222. "CodeForces-1144F": """two-color the graph
  223. if an edge has equal-colored endpoints: output NO
  224. otherwise orient every edge from color 0 to color 1
  225. translate each orientation back to a 0/1 character in input order""",
  226. "CodeForces-1354E": """two-color every connected component and store its two sides
  227. group-knapsack: from each component choose one side so total chosen size is n2
  228. recover choices; label chosen side 2
  229. among all remaining vertices label any n1 as 1 and the rest as 3""",
  230. "CodeForces-741C": """make one vertex for each chair
  231. add an inequality edge for every romantic pair
  232. add inequality edges (1,2), (3,4), ...
  233. two-color every resulting even-cycle component
  234. output the two colors at each romantic pair's chair positions""",
  235. "POJ-1274": """match_right[stall] = unmatched
  236. for each cow u:
  237. clear visited stalls
  238. DFS through acceptable stalls:
  239. take an empty stall, or recursively move its currently matched cow
  240. count successful augmentations""",
  241. "POJ-3041": """rows are left vertices; columns are right vertices
  242. each asteroid adds a row-column edge
  243. maximum_matching = Kuhn(graph)
  244. output maximum_matching # equals minimum vertex cover by Konig""",
  245. "CodeForces-1139E": """potential values are left vertices; clubs are right vertices
  246. start with students that never get deleted and maintain a matching
  247. advance mex while the current potential can be augmented
  248. process deletions in reverse:
  249. record current mex for that day
  250. add the deleted student's potential-club edge
  251. continue augmenting from current mex""",
  252. "HDU-2255": """initialize left label lx[x] = max_y weight[x][y], right label ly = 0
  253. for every left vertex root:
  254. search an augmenting path using only equality edges lx+ly == weight
  255. if search fails:
  256. delta = minimum slack of every unvisited right vertex
  257. subtract delta from visited lx; add it to visited ly; update slack
  258. repeat until root is matched
  259. output weight sum of the perfect matching""",
  260. "CodeForces-498C": """factor all array values and iterate over each distinct prime p
  261. capacity[i] = exponent of p in a[i]
  262. build flow network: source->odd indices, even indices->sink
  263. for every allowed pair add infinite-capacity odd->even edge
  264. answer += maximum_flow(network)
  265. output answer""",
  266. }
  267. BLOCK = re.compile(r"```(?:cpp|c\+\+)\n(.*?)```", flags=re.DOTALL)
  268. VJUDGE = re.compile(r"https://vjudge\.net/problem/([A-Za-z0-9_-]+)")
  269. def main() -> int:
  270. course = Path("课程")
  271. attachment_root = Path("参考实现")
  272. moved = 0
  273. for document in sorted(course.rglob("*.md")):
  274. text = document.read_text(encoding="utf-8")
  275. output: list[str] = []
  276. cursor = 0
  277. for match in BLOCK.finditer(text):
  278. code = match.group(1)
  279. if not re.search(r"\bint\s+main\s*\(", code):
  280. continue
  281. preceding = text[: match.start()]
  282. queries = VJUDGE.findall(preceding)
  283. if not queries:
  284. raise RuntimeError(f"no preceding VJudge ID for {document}")
  285. query = queries[-1]
  286. if query not in PSEUDOCODE:
  287. raise RuntimeError(f"missing pseudocode for {query} in {document}")
  288. topic = document.parent.name
  289. attachment = attachment_root / topic / f"{query}.cpp"
  290. attachment.parent.mkdir(parents=True, exist_ok=True)
  291. attachment.write_text(code.rstrip() + "\n", encoding="utf-8")
  292. relative = Path("../..") / attachment
  293. replacement = (
  294. "```text\n"
  295. + PSEUDOCODE[query].rstrip()
  296. + "\n```\n\n"
  297. + f"完整 GNU C++17 参考实现见附件:[{query}.cpp]({relative.as_posix()})"
  298. )
  299. output.append(text[cursor : match.start()])
  300. output.append(replacement)
  301. cursor = match.end()
  302. moved += 1
  303. if output:
  304. output.append(text[cursor:])
  305. document.write_text("".join(output), encoding="utf-8")
  306. print(f"moved={moved}")
  307. return 0
  308. if __name__ == "__main__":
  309. raise SystemExit(main())