← Back to the problem

India INAIO 2026 · Correction by IOAI Training

Stage 1 Test (18 January 2026)

This page gives the full approach. Read it once you have searched.

Metric
points (70, exact answers)
Evaluated on
written answers
Our score
all 30 items solved, verified in Python
Expected level
exact answers (no qualifying mark published)

Expected level reached

We solved the problem ourselves: downloaded the data, ran the experiments and measured the score below. The statement and data belong to their organisers.

The problem in brief

This is the first-round online test of the Indian National AI Olympiad 2026, sat on 18 January 2026. It has three kinds of items. Section A1 is puzzle-style reasoning: decoding a toy language, choosing which LLM-made features to trust, designing a similarity function, a recursive epidemic, a knight's path. Section A2 contains short modelling puzzles (optimal facility location under two distances, chess match probabilities, reading a price model off a graph, constrained optimisation, Bayes). Section B has four multi-question case studies: a three-class hospital triage classifier, ranking metrics for a retrieval problem, 1-D convolution kernels and pruning a linear classifier. Most answers are an option letter or an integer, usually written as a floor ⌊⋅⌋\lfloor\cdot\rfloor.

Every numerical answer below was checked by exact or brute-force computation in Python (see the solution code).

How you are scored

Each item is worth the points printed next to it: 3 points for each A1 and A2 item, 1 to 4 points for each case-study question; the total is 70 points. Answers are exact (an option or an integer), so there is no partial credit within a question. No qualifying score is published in the statement.

Answers at a glance

itemanswer
A1 Imperial glyphs樹 造 宮 美 (the options are missing from the published PDF)
A1 Faulty oracleB: {F1,F2,F6}\{F_1, F_2, F_6\}
A1 Student searchC
A1 V-67 outbreakC: 281
A1 PikachuE: 25
A2 Godown11
A2 Blind board gambit43
A2 Crude curve92
A2 Catalyst69
A2 Pit-stop86
B1 Triage Q1 to Q720, 85, C, unchanged, unchanged, option 4 (claims 1, 2, 4), 95
B2 WonkaRank Q1 to Q525, option 1, option 6 (none), option 4 (ScoreD only), 27
B3 Convolution Q1 to Q5A, C, C, B, D
B4 Pruning Q1 to Q35, 42, 43

Section A1

Cracking the Imperial Glyphs

Align the sentences that share words. "Apple" appears in sentences 1 to 4 and the glyph pair 蘋果 appears in exactly those, so 蘋果 = apple (two glyphs for one English word). Then 這 appears in "This is..." and "There is...", so it means "this/there is"; 美 = beautiful, 樹 = tree, 宮 = palace, 山 = mountain, 黑 = black, 造 = make, 塗 = paint.

Now the grammar. Two sentences have the same shape as ours:

  • "Does the tree make the apple beautiful?" = 樹 造 蘋果 美 ?
  • "The mountain paints the palace black." = 山 塗 宮 黑

Both follow subject, verb, object, resulting adjective. A statement drops the question mark. So "The tree makes the palace beautiful" is

樹 造 宮 美.\text{樹 造 宮 美} .

(The answer options are images that did not survive in the published PDF; pick the option that shows exactly these four glyphs in this order. Options with 美 before 宮, or with 這 at the start, are the traps.)

Filtering Fakes with a Faulty Oracle: answer B

Ask, for each feature, whether a small offline model can structurally produce it:

  • F1, character count: LLMs read tokens, not characters, so counting characters is a known weakness; a one-line Python len() does it exactly anyway.
  • F2, publication year: with no internet and a fixed training cut-off, the model can only guess, and it cannot know events after its cut-off at all.
  • F6, named outlet: the model has no source information; it will hallucinate a plausible outlet, and this feature leaks style biases rather than facts.

The others (tone, image-headline match, language, topic, presence of a quote) are judgements about the visible content, which is what such a model does reasonably well. Answer B: {F1,F2,F6}\{F_1, F_2, F_6\}.

Smart Student Search: answer C

Check each option against the five rules. The Jaro similarity equals 1 exactly when the two strings are identical, and lies in [0,1][0,1].

  • A gives first and last names equal weight: breaks rule 4.
  • B multiplies by JD(c,c′)\mathrm{JD}(c, c'), which is positive for different but similar section codes (9C and 9B): breaks rule 2. It also penalises last names twice as much: breaks rule 4.
  • C =1c=c′exp⁡ ⁣(−2(1−JD(f,f′))−(1−JD(l,l′)))= \mathbb 1_{c=c'}\exp\!\big(-2(1-\mathrm{JD}(f,f')) - (1-\mathrm{JD}(l,l'))\big): zero when sections differ; the exponent is ≤0\le 0 so the value is in (0,1](0,1]; it equals 1 only when both Jaro terms are 1, i.e. both names identical; Jaro is symmetric; the first-name gap is weighted 2, the last-name gap 1. All rules hold.
  • D weights the last name twice: breaks rule 4.
  • E weights by the lengths of tt's names only, so Sim(s,t)≠Sim(t,s)\mathrm{Sim}(s,t) \ne \mathrm{Sim}(t,s) in general: breaks rule 3.
  • F can reach 3: breaks rule 5.

Recursive V-67 Outbreak: answer C, 281

Each day: Xt=2Nt+4MtX_t = 2N_t + 4M_t; the newcomers add 12Xt\frac12 X_t normal and 14Xt\frac14 X_t mutant cases; half of the existing cases recover. So

Nt+1=12Nt+12Xt,Mt+1=12Mt+14Xt.N_{t+1} = \tfrac12 N_t + \tfrac12 X_t, \qquad M_{t+1} = \tfrac12 M_t + \tfrac14 X_t .
dayNtN_tMtM_tXtX_tNt+MtN_t + M_t
0884816
1281612044
27438300112
318794281

Pikachu's Path to Victory: answer E, 25

A knight's move changes the colour of the square (on a chessboard colouring), and (0,0) and (4,4) have the same colour, so any path between them visits an odd number of squares. The 5x5 board has 13 squares of the starting colour and 12 of the other, so a path through all 25 squares is not ruled out by parity. A depth-first search finds one: an open knight's tour from corner to opposite corner.

A 5 by 5 board with the squares numbered 1 to 25 in the order of a knight's tour that starts at the top-left corner and ends at the bottom-right corner.

Section A2

The Phulera Godown Showdown: 11

Sachivji (Manhattan). ∑i∣x−xi∣+∣y−yi∣\sum_i |x - x_i| + |y - y_i| separates into an xx part and a yy part, and each is minimised by a median. With 4 points the median is any value between the 2nd and 3rd order statistics: x∈[80,120]x \in [80, 120], y∈[80,140]y \in [80, 140]. The rule "take the midpoint of the range" gives GS=(100,110)G_S = (100, 110).

Pradhanji (generalised KL divergence). ∑ixiln⁡xiu+(u−xi)\sum_i x_i \ln\frac{x_i}{u} + (u - x_i) is convex in uu, and its derivative is −1u∑ixi+4-\frac{1}{u}\sum_i x_i + 4. Setting it to 0 gives u=14∑ixiu = \frac14 \sum_i x_i, the mean. So GP=(110,105)G_P = (110, 105).

D=102+52=125≈11.18,⌊D⌋=11.D = \sqrt{10^2 + 5^2} = \sqrt{125} \approx 11.18, \qquad \lfloor D \rfloor = 11 .

Here is the lesson: absolute error leads to the median, squared error or KL-type divergence to the mean.

The Blind Board Gambit: 43

Anand believes Gukesh plays one of the 5 non-canonical orderings uniformly. For each of his 6 orderings we compute the probability of winning at least 2 of 3 boards, averaged over those 5 orderings. With the Elo formula, the best for Anand is (Y, Z, X), with expected win probability 0.338. Gukesh actually plays (C, A, B):

boardGukeshAnandP(Anand wins)
1C 1600Y 17000.640
2A 2000Z 15000.053
3B 1800X 19000.640

p=q1q2q3+q1q2(1−q3)+q1(1−q2)q3+(1−q1)q2q3≈0.434p = q_1 q_2 q_3 + q_1 q_2 (1-q_3) + q_1 (1-q_2) q_3 + (1-q_1) q_2 q_3 \approx 0.434, so ⌊100p⌋=43\lfloor 100p \rfloor = 43.

The Crude Curve Forecast: 92

Read each term of the model off its "fingerprint" on the graph:

  • Seasonality: peaks at t=3,15,27t = 3, 15, 27 give period 12, so τE=12\tau_E = 12; the peaks are where sin⁡=1\sin = 1.
  • Trend and level (before t=30t = 30, where only A+Bt+Esin⁡A + Bt + E\sin is active): P(3)=A+3B+E=34P(3) = A + 3B + E = 34, P(9)=A+9B−E=14P(9) = A + 9B - E = 14, P(15)=A+15B+E=42P(15) = A + 15B + E = 42. Subtracting, 12B=812B = 8, so B=2/3B = 2/3; then A=20A = 20 and E=12E = 12. Checks: P(0)=20P(0) = 20, P(21)=22P(21) = 22, P(27)=50P(27) = 50, P(30−)=40P(30^-) = 40, all as drawn.
  • Jump at t=30t = 30 from 40 to 60: only the Heaviside term D h(t−τD)D\,h(t-\tau_D) can jump (the CC term starts at 0), so τD=30\tau_D = 30, D=20D = 20.
  • Smooth bend from t=48t = 48: at t=48t = 48 the model without CC gives 20+32+20+0=7220 + 32 + 20 + 0 = 72, exactly the marked point, so the CC term starts there: τC=48\tau_C = 48. Afterwards the curve sits lower: without CC the value at the trough t=57t = 57 would be 66, but the graph shows 56, and at t=60t = 60 it would be 80 but the graph ends at 70. So C=−10C = -10 (the factor 1−e−91 - e^{-9} is 1 for practical purposes).

Then

P(75)=20+23⋅75+20−10 (1−e−27)+12sin⁡ ⁣(2π⋅7512)=20+50+20−10+12+10e−27,P(75) = 20 + \tfrac23\cdot 75 + 20 - 10\,(1 - e^{-27}) + 12\sin\!\big(\tfrac{2\pi\cdot 75}{12}\big) = 20 + 50 + 20 - 10 + 12 + 10e^{-27},

since 75/12=6.2575/12 = 6.25 puts the sine at a peak. So P(75)P(75) is a hair above 92 and ⌊P(75)⌋=92\lfloor P(75) \rfloor = 92.

The reconstructed model drawn from 0 to 78 months: it passes through all the values read on the official graph, jumps by 20 at month 30, bends down smoothly after month 48, and reaches 92 at month 75.

The Catalyst Constraint: 69

Maximise 16cA−2cA2+24cB−3cB216c_A - 2c_A^2 + 24c_B - 3c_B^2 with cA+cB≤5c_A + c_B \le 5, cA,cB≥0c_A, c_B \ge 0. Unconstrained, each pathway peaks at cA=16/4=4c_A = 16/4 = 4 and cB=24/6=4c_B = 24/6 = 4, which would need 8 moles of catalyst. So the constraint binds and the marginal yields must be equal:

16−4cA=24−6cB,cA+cB=5  ⇒  cB=2.8,  cA=2.2.16 - 4c_A = 24 - 6c_B, \quad c_A + c_B = 5 \;\Rightarrow\; c_B = 2.8,\; c_A = 2.2 .

dA=35.2−9.68=25.52d_A = 35.2 - 9.68 = 25.52, dB=67.2−23.52=43.68d_B = 67.2 - 23.52 = 43.68, total 69.269.2, so the answer is 69. (Equal marginal returns at the optimum is the same idea as a Lagrange multiplier, or as a learning-rate budget split between two objectives.)

The Pit-Stop Paradox: 86

Each (weather, strategy) pair gets probability P(weather) P(strategy∣weather) P(H∣strategy,weather)P(\text{weather})\,P(\text{strategy}\mid\text{weather})\,P(H\mid\text{strategy},\text{weather}):

neitheronly Vonly Hboth
no rain (x 0.5)0.056250.120.0050.04125
rain (x 0.5)0.01250.00250.04750.225

P(H wins)=0.51P(H \text{ wins}) = 0.51, of which "neither pitted" accounts for 0.068750.06875. Hence

P(at least one pitted∣H)=1−0.068750.51≈0.865,⌊86.5⌋=86.P(\text{at least one pitted} \mid H) = 1 - \frac{0.06875}{0.51} \approx 0.865, \qquad \lfloor 86.5 \rfloor = 86 .

Section B1: Triage classifier

First rebuild the full confusion matrix of the 1000 test patients (600 / 300 / 100 per class). Sensitivities give the diagonal (510, 210, 80); the two "egregious error" rates give 1→31 \to 3: 0.0333×600=200.0333 \times 600 = 20 and 3→13 \to 1: 0.10×100=100.10 \times 100 = 10. Then each row sums to its class size, so 1→21 \to 2 is 70 and 3→23 \to 2 is 10. The last unknown is how class 2's 90 errors split between classes 1 and 3. Specificity of class 1 decides it: 10 % of the 400 non-class-1 patients, i.e. 40, are predicted class 1, of which 10 come from class 3, so 2→12 \to 1 is 30 and 2→32 \to 3 is 60.

actual \ predicted123
1 (600)5107020
2 (300)3021060
3 (100)101080

Check: this matrix gives specificities 0.900, 0.886 and 0.911, matching the stated 0.90, 0.89 and 0.91.

  • Q1: 20. Missed ICU patients are 1−sensitivity3=20%1 - \text{sensitivity}_3 = 20\%.
  • Q2: 85. This is just the sensitivity of class 1.
  • Q3: C. Expected ICU flags per day =200 (0.10 TPR+0.90 FPR)= 200\,(0.10\,\text{TPR} + 0.90\,\text{FPR}): A 15.6, B 21.6, C 24.6, D 31.2. C is the most sensitive point that stays under 25 beds.
  • Q4: the curve is unchanged. A ROC curve only depends on the ranking of the scores. A strictly increasing transformation keeps every ranking, so each threshold tt has an equivalent threshold t1.5t^{1.5} with the same TPR and FPR. (Strictly, s1.5s^{1.5} is only a real, increasing function for s≥0s \ge 0; the statement calls it monotonic, which is what matters.)
  • Q5: the curve is unchanged. TPR and FPR are computed within each class, so changing the class proportions does not move the curve (as long as the score distributions within each class stay the same). What changes is precision and the number of flagged patients: at 25 % prevalence the bed constraint of Q3 would select a different point.
  • Q6: option 4 (claims 1, 2 and 4). Claim 1 holds: two independent binary classifiers can contradict each other ("not class 1" by the first and "not class 3" by the second is consistent, but "class 1" by the first and "class 3" by the second is not), and pairwise votes can form cycles. Claim 2 holds: the domain analysis says the two boundaries depend on different criteria, which a single score with two thresholds (parallel boundaries) cannot represent, but Y can. Claim 3 is false: Z throws away the ordering, trains each classifier on only part of the limited data, and its 1-vs-3 classifier adds little. Claim 4 holds: squared loss on ordinal targets acts as regression, which respects the order 1 < 2 < 3, while hinge loss only cares about the margin; but squared loss also punishes points that are far on the correct side and is dominated by outliers. Claim 5 is false: squared loss grows quadratically with the error, so it is less robust to outliers than logistic loss, which grows only linearly.
  • Q7: 95. Multiply the confusion matrix by the benefit matrix cell by cell: 510⋅100+70⋅50+210⋅100+60⋅50+10⋅50+80⋅200=95 000510\cdot100 + 70\cdot50 + 210\cdot100 + 60\cdot50 + 10\cdot50 + 80\cdot200 = 95\,000, i.e. 95 points per patient.

Section B2: WonkaRank

There are R=4R = 4 relevant chocolates: D1 (10), D5 (3), D8 (5), D9 (3).

  • Q1: 25. The top 5 bought are D2, D3, D5, D6, D7: only D5 is relevant, so recall is 1/41/4.
  • Q2: option 1. Strategy A gives a positive score only to D1, D2, D5, D8, so it effectively retrieves 4 chocolates, 3 of them relevant: precision 0.75, recall 0.75. Strategy B also gives score 1 to D3, D4, D7, D9, so it retrieves 8 chocolates (D6 alone scores 0), now including D9: recall 1, precision 4/8 = 0.5. B improves recall but reduces precision.
  • Q3: option 6, none of the above. Compute rather than guess. DCGA=31+10log⁡23+52=11.81\mathrm{DCG}_A = \frac{3}{1} + \frac{10}{\log_2 3} + \frac{5}{2} = 11.81 and DCGB=51+3log⁡23+102=11.89\mathrm{DCG}_B = \frac{5}{1} + \frac{3}{\log_2 3} + \frac{10}{2} = 11.89. B wins narrowly, yet B places the most relevant chocolate (D1) later than A does (rank 3 against rank 2). So the explanation cannot be "DCG puts the highest-relevance item first" (option 2). DCG simply adds discounted gains, and B's gain from putting 5 at rank 1 and 3 at rank 2 slightly outweighs A's. The other options are false outright: order matters, gains are graded, all ranks count, irrelevant items contribute 0. (Option 2 may be the intended answer, but the numbers contradict it.)
  • Q4: option 4, ScoreD only. Scores of the top documents: ScoreA puts D2 and D5 first (7 each), giving precision 1/2. ScoreB counts the ingredients present: D2 and D5 again (3 each), precision 1/2. ScoreC ties D1, D2, D5, D8 at 4, and the ID tie-break keeps D1, D2, precision 1/2. ScoreD =∑diwi1+∑di= \frac{\sum d_i w_i}{1 + \sum d_i} rewards concentration on the heavy ingredients and puts D1 and D8 (score 2) first: precision 1. Normalising by the number of ingredients penalises chocolates that are merely "large", such as D2 with caramel added.
  • Q5: 27. ScoreA ranking: D2, D5, D1, D8, D3, ... The relevant chocolates in the top 5 are at ranks 2, 3, 4, with inspection probabilities 0.5, 0.3, 0.3. PI-Recall@5 =(0.5+0.3+0.3)/4=0.275= (0.5 + 0.3 + 0.3)/4 = 0.275.

Section B3: Convolution kernels

The definition yi=∑jhjxi+k−jy_i = \sum_j h_j x_{i+k-j} is a true convolution: the kernel is reversed before sliding. A useful trick: the sum S=∑iyiS = \sum_i y_i of a zero-sum kernel telescopes and only depends on a few values at the ends of the signal.

  • Q1: A, [8, 5, 11]. The padded signal is [3,2,1,3,4,3][3, 2, 1, 3, 4, 3] and the reversed kernel is [2,1][2, 1]. With stride 2 the windows are (3,2),(1,3),(4,3)(3,2), (1,3), (4,3): 6+2=86+2 = 8, 2+3=52+3 = 5, 8+3=118+3 = 11.
  • Q2: C, [1, -2, 1]. A constant signal must give S=0S = 0, so the kernel must sum to 0, which leaves B, C, D and F. For [1,−2,1][1,-2,1], S=x1−x2−xn−1+xnS = x_1 - x_2 - x_{n-1} + x_n: it is 0 for the smooth xx and −10-10 for the fluctuating zz. Each first-difference kernel instead gives ∣S∣|S| larger for xx than for zz or about equal ([−1,0,1][-1,0,1]: −12-12 vs −6-6; [−1,1,0][-1,1,0]: −6-6 vs 22; [1,0,−1][1,0,-1]: 1212 vs 66). A second difference measures curvature, i.e. changes of direction.
  • Q3: C, [1, -2, 1]. The discrete second derivative xi−2xi+1+xi+2x_i - 2x_{i+1} + x_{i+2} is 0 at constant velocity. For the accelerating xx it gives 1,1,11, 1, 1 (S=3>0S = 3 > 0), for the decelerating ww it gives −1,−1,−1-1, -1, -1 (S=−3S = -3).
  • Q4: B, [-1, 2, -1]. This is minus the second difference, so it is 0 on any straight line (constant or linear), and therefore 0 for the smooth zz. On xx it gives −4,10,−4,−2-4, 10, -4, -2, so Sabs=20S_{\text{abs}} = 20, with the big response centred on the spike. Using SabsS_{\text{abs}} rather than SS matters: the plain sum would telescope back to a few end values.
  • Q5: D, [1, -1, 1, -1]. A length-4 kernel on a length-4 signal gives a single output y=h1x4+h2x3+h3x2+h4x1y = h_1x_4 + h_2x_3 + h_3x_2 + h_4x_1. For it to vanish on every symmetric signal (x1=x4x_1 = x_4, x2=x3x_2 = x_3) we need h4=−h1h_4 = -h_1 and h3=−h2h_3 = -h_2; only D satisfies this. It gives y=0y = 0 on [2,5,5,2][2,5,5,2] and y=−3≠0y = -3 \ne 0 on zz. Caveat: no single linear output can be nonzero on every asymmetric signal (D also gives 0 on [1,1,2,2][1,1,2,2]), because the asymmetric part of a length-4 signal has two degrees of freedom; detecting all of them would need two outputs.

Section B4: Pruning a linear classifier

  • Q1: 5. The first four rows of XX are the identity, so s1,…,s4s_1, \dots, s_4 are w1,…,w4w_1, \dots, w_4; then s5=w1+w2s_5 = w_1 + w_2 and s6=w1+w3+w4s_6 = w_1 + w_3 + w_4. Option A: w1=2w_1 = 2, w3=1w_3 = 1, w4=−1w_4 = -1 and w2=s5−w1=−1w_2 = s_5 - w_1 = -1, so s=(2,−1,1,−1,1,2)s = (2, -1, 1, -1, 1, 2): every sign matches yy, accuracy 100 %. Option B: w1=3w_1 = 3, w2=−5w_2 = -5, w4=−1w_4 = -1 and w3=1−3+1=−1w_3 = 1 - 3 + 1 = -1, so s3=−1s_3 = -1 and s5=−2s_5 = -2: two wrong signs, accuracy 4/6. Choose A: ∥w∥1=2+1+1+1=5\lVert w\rVert_1 = 2 + 1 + 1 + 1 = 5.
  • Q2: 42. Unpruned, every example is correctly classified (scores 1,1,1,1,1,1,01,1,1,1,1,1,0, with 0 counting as class 0). Feature 7 already has weight 0, so pruning it costs nothing; every other feature carries a whole example. Removing feature j≤5j \le 5 sets that example's score to 0, so it flips to class 0, a mistake, and also shifts the last example's score to +1+1, a second mistake. Removing feature 6 only costs example 6. So the best pair is {6,7}\{6, 7\} with accuracy 6/7, and 6⋅7=426 \cdot 7 = 42. (Exhaustive search over the 21 pairs confirms it is the unique best.)
  • Q3: 43. After pruning, the scores are (1,1,1,1,1,0,−5)(1,1,1,1,1,0,-5). Any bias b≥1b \ge 1 makes example 6 positive while example 7 stays negative (−5+3<0-5 + 3 < 0): accuracy 100 %. Negative biases push the five ones to ≤0\le 0. The smallest ∣b∣|b| with full accuracy is b∗=1b^\ast = 1, so the answer is 42+1=4342 + 1 = 43. (The mean-preserving correction of the background section agrees: the removed contribution is (0,0,0,0,0,1,5)(0,0,0,0,0,1,5), so μS=6/7≈0.86\mu_S = 6/7 \approx 0.86, which also restores 100 % accuracy.)

What to remember

  • Turn every metric question into a confusion matrix. Sensitivities, specificities and error rates are just row and column sums; once the matrix is rebuilt, benefits, capacities and precision all follow by arithmetic.
  • ROC curves only see rankings within each class. Monotone transforms of the score and changes in class prevalence leave them unchanged. Prevalence still matters for precision, for the number flagged and for choosing the operating point.
  • The loss decides the optimal summary. Absolute error gives the median, squared error and KL-type divergence give the mean, and equal marginal returns at the optimum is the Lagrange condition.
  • Compute, then match the option. On DCG the intuitive choice ("highest relevance first") is contradicted by the numbers. Small brute-force scripts (DFS for the knight, enumeration for the pruning pairs) settle what intuition cannot.
  • Kernels are finite-difference operators. [1,−1][1,-1] measures slope, [1,−2,1][1,-2,1] curvature or acceleration, [−1,2,−1][-1,2,-1] spikes. Zero-sum kernels ignore constants, and sums of their outputs telescope.
Full code (solution.py)
"""INAIO 2026 Stage 1 (18 Jan 2026): brute-force / exact checks of every numerical answer."""
import itertools, numpy as np, math
from fractions import Fraction as Fr
# outbreak
N,M=Fr(8),Fr(8)
for t in range(3):
    X=2*N+4*M; N,M = N/2 + X/2, M/2 + X/4; print('day',t+1,N,M,N+M)
# pikachu: longest knight path (0,0)->(4,4), no revisits
moves=[(1,2),(2,1),(-1,2),(-2,1),(1,-2),(2,-1),(-1,-2),(-2,-1)]
best=[0,None]
def dfs(p,vis,path):
    if p==(4,4):
        if len(path)>best[0]: best[0]=len(path); best[1]=list(path)
        return
    for dx,dy in moves:
        q=(p[0]+dx,p[1]+dy)
        if 0<=q[0]<5 and 0<=q[1]<5 and q not in vis:
            vis.add(q); path.append(q); dfs(q,vis,path); path.pop(); vis.remove(q)
dfs((0,0),{(0,0)},[(0,0)]); print('pikachu',best)
# chess
P=lambda R,S: 1/(1+10**((S-R)/400))
G={'A':2000,'B':1800,'C':1600}; An={'X':1900,'Y':1700,'Z':1500}
def anand_win(g,a):
    ps=[1-P(G[x],An[y]) for x,y in zip(g,a)]
    return sum(np.prod([ps[i] if w else 1-ps[i] for i,w in enumerate(r)]) for r in itertools.product([0,1],repeat=3) if sum(r)>=2)
gs=[g for g in itertools.permutations('ABC') if g!=('A','B','C')]
res={a:np.mean([anand_win(g,a) for g in gs]) for a in itertools.permutations('XYZ')}
for a,v in sorted(res.items(),key=lambda kv:-kv[1]): print(a,round(v,5))
abest=max(res,key=res.get); p=anand_win(('C','A','B'),abest); print('chess', abest, p, math.floor(100*p))
# pit stop
nr={'N':(.45,.25),'V':(.30,.80),'H':(.10,.10),'B':(.15,.55)}; r={'N':(.05,.50),'V':(.10,.05),'H':(.10,.95),'B':(.75,.60)}
joint={('nr',k):.5*a*b for k,(a,b) in nr.items()}|{('r',k):.5*a*b for k,(a,b) in r.items()}
ph=sum(joint.values()); pit=sum(v for (w,k),v in joint.items() if k!='N'); print('pit', ph, pit/ph, math.floor(100*pit/ph))
# B4 Q2/Q3
X=np.array([[-1,0,0,0,0,0,3],[0,-1,0,0,0,0,0],[0,0,-1,0,0,0,-2],[0,0,0,-1,0,0,6],[0,0,0,0,-1,0,7],[0,0,0,0,0,1,-6],[1,1,1,1,1,5,-9]])
w=np.array([-1,-1,-1,-1,-1,1,0]); y=np.array([1,1,1,1,1,1,0])
acc=lambda s:((s>0).astype(int)==y).mean()
print('base',X@w,acc(X@w))
pr={}
for j,k in itertools.combinations(range(7),2):
    ww=w.copy(); ww[[j,k]]=0; pr[(j+1,k+1)]=acc(X@ww)
mx=max(pr.values()); print('best prune', mx, [k for k,v in pr.items() if v==mx])
for jk in [k for k,v in pr.items() if v==mx]:
    ww=w.copy(); ww[[jk[0]-1,jk[1]-1]]=0; s=X@ww
    print(jk, s, {b:round(acc(s+b),3) for b in range(-3,4)})
# catalyst
cs=np.linspace(0,5,500001); tot=16*cs-2*cs**2+24*(5-cs)-3*(5-cs)**2; print('catalyst',cs[tot.argmax()],tot.max())
# godown: L1 median vs generalised-KL minimiser (= arithmetic mean)
xs, ys = [40, 120, 200, 80], [180, 80, 140, 20]
S = ((sorted(xs)[1] + sorted(xs)[2]) / 2, (sorted(ys)[1] + sorted(ys)[2]) / 2); Pp = (np.mean(xs), np.mean(ys))
print('godown', S, Pp, math.floor(math.dist(S, Pp)))
# crude oil: parameters read off the graph
A_, B_, E_, D_, C_ = 20, 2/3, 12, 20, -10
P75 = A_ + B_*75 + C_*(1 - math.exp(-(75-48))) + D_ + E_*math.sin(2*math.pi*75/12); print('crude', P75, math.floor(P75))
# triage confusion matrix (rows = actual 1,2,3 ; columns = predicted 1,2,3)
CM = np.array([[510, 70, 20], [30, 210, 60], [10, 10, 80]]); Bn = np.array([[100, 50, 0], [0, 100, 50], [0, 50, 200]])
for c in range(3):
    tp = CM[c, c]; sens = tp / CM[c].sum(); neg = CM.sum() - CM[c].sum(); fp = CM[:, c].sum() - tp
    print(f'class {c+1}: sensitivity {sens:.3f} specificity {(neg-fp)/neg:.3f}')
print('triage benefit', (CM*Bn).sum()/1000)
for name, f, tpr in [('A',.02,.6),('B',.04,.72),('C',.05,.78),('D',.08,.84)]: print('ICU flagged/day', name, 200*(.1*tpr+.9*f))
# WonkaRank
docs = {1:(1,1,0,0),2:(1,1,1,0),3:(1,0,1,0),4:(0,1,0,0),5:(1,1,0,1),6:(0,0,1,1),7:(1,0,0,0),8:(1,1,0,0),9:(0,1,1,0)}
rel = {1:10,5:3,8:5,9:3}; W = (3,3,1,1)
dcg = lambda L: sum(rel.get(d,0)/math.log2(i+2) for i,d in enumerate(L)); print('DCG A', dcg([5,1,8,2]), 'DCG B', dcg([8,5,1,2]))
sc = {'A': lambda d: sum(a*b for a,b in zip(d,W)), 'B': lambda d: sum(min(1,a)*min(1,b) for a,b in zip(d,W)),
      'C': lambda d: sum(a*b for a,b in zip(d,W)) - sum(d), 'D': lambda d: sum(a*b for a,b in zip(d,W))/(1+sum(d))}
for n, f in sc.items():
    rank = sorted(docs, key=lambda k: (-f(docs[k]), k)); print('score', n, rank[:2], 'P@2', sum(k in rel for k in rank[:2])/2)
rank = sorted(docs, key=lambda k: (-sc['A'](docs[k]), k)); p = [.5,.5,.3,.3,.2]
print('PI-recall@5', sum(pi for pi, d in zip(p, rank[:5]) if d in rel)/4)
# convolutions (true convolution: kernel reversed)
conv = lambda x, h, s=1: [sum(h[j]*x[i+len(h)-1-j] for j in range(len(h))) for i in range(0, len(x)-len(h)+1, s)]
print('conv Q1', conv([3,2,1,3,4,3], [1,2], 2))
for h in ([-1,0,1],[1,-2,1],[-1,1,0],[1,0,-1]): print('Q2', h, sum(conv([2,4,6,8,10],h)), sum(conv([2,6,3,10,4],h)))
print('Q3', sum(conv([1,2,4,7,11],[1,-2,1])), sum(conv([1,5,8,10,11],[1,-2,1])))
print('Q4', sum(map(abs, conv([2,3,8,3,2,3],[-1,2,-1]))), sum(map(abs, conv([2,3,4,5,6,7],[-1,2,-1]))))
print('Q5', conv([2,5,5,2],[1,-1,1,-1]), conv([1,2,3,-1],[1,-1,1,-1]), conv([1,1,2,2],[1,-1,1,-1]))