| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- #!/usr/bin/env python3
- """Move standalone C++ solutions out of lessons and insert core pseudocode.
- This is an idempotent course-layout migration. It identifies the nearest
- preceding VJudge problem link for each fenced block containing main(), writes
- the block to 参考实现/<topic>/<OJ-id>.cpp, and replaces it with concise
- Python-like pseudocode plus a relative attachment link.
- """
- from __future__ import annotations
- import re
- from pathlib import Path
- PSEUDOCODE: dict[str, str] = {
- "POJ-2230": """for each road (u, v):
- add_arc(u, v); add_arc(v, u)
- stack = [1]; reversed_route = []
- while stack:
- u = stack[-1]
- if u still has an unused outgoing arc: stack.append(take_one_arc(u))
- else: reversed_route.append(stack.pop())
- output reverse(reversed_route)""",
- "HDU-1116": """for each word:
- add directed edge first_letter -> last_letter
- update in_degree, out_degree, and undirected DSU
- connected = all nonzero-degree letters have one DSU root
- degree_ok = all differences are 0, or exactly one +1 and one -1
- answer = connected and degree_ok""",
- "CodeForces-508D": """encode every length-2 string as a vertex
- for trigram xyz: add edge xy -> yz
- check directed Euler degree conditions and select start
- path = Hierholzer(start)
- if len(path) != edge_count + 1: print NO
- else: print first 2-char vertex, then append each later vertex's last char""",
- "CodeForces-723E": """answer = number of even-degree original vertices
- add dummy vertex 0 and edge 0-v for every odd-degree vertex v
- for every augmented connected component:
- follow an Euler circuit and orient each traversed original edge forward
- discard dummy edges and output recorded original orientations""",
- "CodeForces-427C": """components = Tarjan_SCC(graph)
- total_cost = 0; ways = 1
- for component in components:
- best = minimum(cost[v] for v in component)
- total_cost += best
- ways *= count(v in component where cost[v] == best) (mod MOD)""",
- "POJ-1236": """component = Tarjan_SCC(graph)
- if there is only one component: output 1, 0
- build condensation indegree and outdegree
- sources = count(indegree == 0)
- sinks = count(outdegree == 0)
- output sources, max(sources, sinks)""",
- "POJ-2186": """component = Tarjan_SCC(graph)
- compute every condensation component's size and outdegree
- sink_components = components with outdegree 0
- if len(sink_components) != 1: output 0
- else: output size of the unique sink component""",
- "CodeForces-1000E": """bridges = find_bridges_with_edge_ids(graph)
- component = flood_fill_while_ignoring(bridges)
- bridge_tree = contract_components_and_keep_bridges()
- a = farthest_vertex(bridge_tree, arbitrary_root)
- b, distance = farthest_vertex(bridge_tree, a)
- output distance""",
- "POJ-3177": """bridges = find_bridges_with_edge_ids(graph)
- component = flood_fill_while_ignoring(bridges)
- degree = degrees in the contracted bridge tree
- leaves = count(degree == 1)
- output ceil(leaves / 2)""",
- "CodeForces-732F": """find bridges and edge-biconnected components
- root = component with maximum number of original vertices
- inside each component: orient DFS-tree edges down and back edges up
- build the bridge tree rooted at root
- orient every bridge from child component toward parent component
- output size(root) and all orientations in input order""",
- "POJ-1144": """DFS(u, parent_edge):
- dfn[u] = low[u] = next_time()
- for each edge u-v except parent_edge:
- if v unvisited:
- DFS(v); low[u] = min(low[u], low[v])
- if u is non-root and low[v] >= dfn[u]: mark u as cut
- else: low[u] = min(low[u], dfn[v])
- root is cut iff it has at least two DFS children""",
- "CodeForces-962F": """during undirected Tarjan, push tree/back edge IDs on a stack
- when low[child] >= dfn[parent]:
- pop through the parent-child edge to obtain one vertex-biconnected block
- vertices = distinct endpoints of popped edges
- if number_of_edges == number_of_vertices:
- add every block edge to answer
- sort and output answer edge IDs""",
- "AtCoder-abc258_g": """neighbor[i] = bitset of vertices adjacent to i
- sum = 0
- for every edge i-j with i < j:
- sum += popcount(neighbor[i] AND neighbor[j])
- output sum / 3""",
- "CodeForces-1097F": """state[x][d] = parity of elements in multiset x divisible by d
- singleton(v): set exactly the divisor bits of v
- union(y, z): state[x] = state[y] XOR state[z]
- gcd_product(y, z): state[x] = state[y] AND state[z]
- query(v): parity = popcount(state[x] AND squarefree_multiple_mask[v]) mod 2""",
- "CodeForces-914F": """position[c] = bitset of indices currently containing c
- update(i, c): clear i in old character set; set i in c
- query(l, r, pattern):
- match = all_ones
- for offset, c in enumerate(pattern):
- match &= position[c] >> offset
- discard start positions outside [l, r-len(pattern)+1]
- return popcount(match)""",
- "POJ-3461": """pi = prefix_function(pattern)
- matched = answer = 0
- for c in text:
- while matched > 0 and c != pattern[matched]: matched = pi[matched-1]
- if c == pattern[matched]: matched += 1
- if matched == len(pattern):
- answer += 1; matched = pi[matched-1]""",
- "POJ-2406": """pi = prefix_function(s)
- period = len(s) - pi[-1]
- if len(s) is divisible by period: output len(s) / period
- else: output 1""",
- "CodeForces-432D": """pi = prefix_function(s)
- occ[pi[i]] += 1 for every position i
- for length from n down to 1: occ[pi[length-1]] += occ[length]
- occ[length] += 1 for each prefix's occurrence at position 0
- borders = n, pi[n-1], pi[pi[n-1]-1], ...
- output reversed(borders) with occ[length]""",
- "CodeForces-471D": """if target length == 1: output source length
- text = adjacent_differences(source_heights)
- pattern = adjacent_differences(target_heights)
- output KMP_occurrence_count(pattern, text)""",
- "POJ-3974": """d1 = odd_palindrome_radii_by_Manacher(s)
- d2 = even_palindrome_radii_by_Manacher(s)
- answer = max(max(2*d1[i]-1), max(2*d2[i]))
- output answer""",
- "CodeForces-17E": """d1, d2 = Manacher(s)
- P = sum(d1) + sum(d2)
- for each center and radius:
- range_add all represented start positions
- range_add all represented end positions
- start, end = prefix_sums(the two difference arrays)
- disjoint = sum(start[i] * number_of_palindromes_ending_before_i)
- output choose(P, 2) - disjoint (mod MOD)""",
- "AtCoder-abc340_d": """for each stage i:
- add edge i -> i+1 with cost A[i]
- add edge i -> X[i] with cost B[i]
- dist = Dijkstra(graph, source=1)
- output dist[N]""",
- "AtCoder-abc277_e": """state = (vertex, currently_passable_edge_label)
- for original edge with label a: connect its endpoints in layer a with cost 1
- for each switch vertex: connect its two layers with cost 0
- dist = zero_one_BFS(start=(1, label_1))
- output min(dist[(N,0)], dist[(N,1)])""",
- "CodeForces-449B": """train[v] = minimum train length to v
- run road-only Dijkstra with seeds (capital,0) and every finite train[v]
- mark v if some road u-v satisfies dist[u] + w == dist[v]
- needed = count(v where train[v] == dist[v] and v has no road predecessor)
- output total_trains - needed""",
- "CodeForces-59E": """BFS state is (previous_vertex, current_vertex); start = (0, 1)
- for neighbor next of current:
- skip if (previous, current, next) is forbidden
- visit state (current, next) and store parent state
- stop at first state whose current vertex is N
- follow parent states to restore the vertex path""",
- "CodeForces-229B": """run Dijkstra from planet 1
- when finalizing u at time t (unless u is destination):
- binary-search t in u's sorted blocked times
- advance depart across the consecutive blocked run t,t+1,...
- relax every edge using depart + edge_weight
- output destination distance or -1""",
- "CodeForces-545E": """run Dijkstra from source
- on shorter distance to v: replace distance and parent edge
- on equal distance: replace parent edge only if its own weight is smaller
- because all weights are positive, chosen parents strictly decrease distance
- output sum and IDs of one chosen parent edge for every non-source vertex""",
- "POJ-3255": """first[source] = 0; second[*] = infinity
- pop a distance label d at u
- for edge u-v:
- candidate = d + weight
- if candidate < first[v]: swap(candidate, first[v]); push first[v]
- if first[v] < candidate < second[v]: set and push second[v]
- output second[destination]""",
- "POJ-3169": """convert every constraint to x[v] <= x[u] + weight and add edge u->v
- add order edges (i+1)->i with weight 0
- if Bellman-Ford from a super-source finds a negative cycle: output -1
- dist = Bellman-Ford(source=1)
- if N is unreachable: output -2
- else: output dist[N], the tightest upper bound on x[N]-x[1]""",
- "AtCoder-arc076_b": """candidates = []
- sort towns by x; add every adjacent pair
- sort towns by y; add every adjacent pair
- give each candidate its true min(|dx|, |dy|) cost
- output Kruskal(candidates)""",
- "CodeForces-1245D": """treat building station i as edge super_source-i with cost c[i]
- initialize Prim key[i] = c[i], parent[i] = super_source
- repeat n times:
- select unused city v with minimum key
- record station if parent is source, otherwise record wire
- update every unused city's key by wire_cost(v, city)
- output total and recorded construction""",
- "CodeForces-1095F": """hub = vertex with minimum a[hub]
- candidates = all special offers
- for every v != hub: add ordinary edge hub-v with cost a[hub]+a[v]
- output Kruskal(candidates)""",
- "CodeForces-609E": """T, total = Kruskal_MST(graph)
- preprocess binary lifting for maximum edge on every tree path
- for edge e=(u,v,w):
- if e belongs to T: answer[e] = total
- else: answer[e] = total + w - maximum_on_tree_path(u,v)""",
- "CodeForces-160D": """sort edges into equal-weight groups; DSU contains only lighter edges
- for each group:
- edges already inside one DSU component are 'none'
- contract DSU roots and build a temporary multigraph from remaining edges
- temporary bridges are 'any'; other remaining edges are 'at least one'
- union all group edges in the global DSU""",
- "CodeForces-1706E": """process input edges by index; every successful DSU merge creates a new parent node
- new node value = current edge index; its children are the two old component roots
- preprocess LCA on the reconstruction tree
- need[i] = value(LCA(original_vertex_i, original_vertex_i+1))
- answer(l,r) = max(need[l .. r-1]), or 0 when l == r""",
- "CodeForces-888G": """sort and deduplicate values
- solve(group, bit):
- split group by current bit
- answer = solve(zero_side, bit-1) + solve(one_side, bit-1)
- if both sides exist:
- build a lower-bit binary trie for the smaller side
- answer += 2^bit + minimum lower-bit XOR across the split
- return answer""",
- "CodeForces-1144F": """two-color the graph
- if an edge has equal-colored endpoints: output NO
- otherwise orient every edge from color 0 to color 1
- translate each orientation back to a 0/1 character in input order""",
- "CodeForces-1354E": """two-color every connected component and store its two sides
- group-knapsack: from each component choose one side so total chosen size is n2
- recover choices; label chosen side 2
- among all remaining vertices label any n1 as 1 and the rest as 3""",
- "CodeForces-741C": """make one vertex for each chair
- add an inequality edge for every romantic pair
- add inequality edges (1,2), (3,4), ...
- two-color every resulting even-cycle component
- output the two colors at each romantic pair's chair positions""",
- "POJ-1274": """match_right[stall] = unmatched
- for each cow u:
- clear visited stalls
- DFS through acceptable stalls:
- take an empty stall, or recursively move its currently matched cow
- count successful augmentations""",
- "POJ-3041": """rows are left vertices; columns are right vertices
- each asteroid adds a row-column edge
- maximum_matching = Kuhn(graph)
- output maximum_matching # equals minimum vertex cover by Konig""",
- "CodeForces-1139E": """potential values are left vertices; clubs are right vertices
- start with students that never get deleted and maintain a matching
- advance mex while the current potential can be augmented
- process deletions in reverse:
- record current mex for that day
- add the deleted student's potential-club edge
- continue augmenting from current mex""",
- "HDU-2255": """initialize left label lx[x] = max_y weight[x][y], right label ly = 0
- for every left vertex root:
- search an augmenting path using only equality edges lx+ly == weight
- if search fails:
- delta = minimum slack of every unvisited right vertex
- subtract delta from visited lx; add it to visited ly; update slack
- repeat until root is matched
- output weight sum of the perfect matching""",
- "CodeForces-498C": """factor all array values and iterate over each distinct prime p
- capacity[i] = exponent of p in a[i]
- build flow network: source->odd indices, even indices->sink
- for every allowed pair add infinite-capacity odd->even edge
- answer += maximum_flow(network)
- output answer""",
- }
- BLOCK = re.compile(r"```(?:cpp|c\+\+)\n(.*?)```", flags=re.DOTALL)
- VJUDGE = re.compile(r"https://vjudge\.net/problem/([A-Za-z0-9_-]+)")
- def main() -> int:
- course = Path("课程")
- attachment_root = Path("参考实现")
- moved = 0
- for document in sorted(course.rglob("*.md")):
- text = document.read_text(encoding="utf-8")
- output: list[str] = []
- cursor = 0
- for match in BLOCK.finditer(text):
- code = match.group(1)
- if not re.search(r"\bint\s+main\s*\(", code):
- continue
- preceding = text[: match.start()]
- queries = VJUDGE.findall(preceding)
- if not queries:
- raise RuntimeError(f"no preceding VJudge ID for {document}")
- query = queries[-1]
- if query not in PSEUDOCODE:
- raise RuntimeError(f"missing pseudocode for {query} in {document}")
- topic = document.parent.name
- attachment = attachment_root / topic / f"{query}.cpp"
- attachment.parent.mkdir(parents=True, exist_ok=True)
- attachment.write_text(code.rstrip() + "\n", encoding="utf-8")
- relative = Path("../..") / attachment
- replacement = (
- "```text\n"
- + PSEUDOCODE[query].rstrip()
- + "\n```\n\n"
- + f"完整 GNU C++17 参考实现见附件:[{query}.cpp]({relative.as_posix()})"
- )
- output.append(text[cursor : match.start()])
- output.append(replacement)
- cursor = match.end()
- moved += 1
- if output:
- output.append(text[cursor:])
- document.write_text("".join(output), encoding="utf-8")
- print(f"moved={moved}")
- return 0
- if __name__ == "__main__":
- raise SystemExit(main())
|