Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete & Prevent zombie editorials (Closes #1958) #2004

Merged
merged 1 commit into from
Jan 4, 2023
Merged

Delete & Prevent zombie editorials (Closes #1958) #2004

merged 1 commit into from
Jan 4, 2023

Conversation

jdabtieu
Copy link
Contributor

@jdabtieu jdabtieu commented Oct 4, 2022

This PR deletes editorials with problem=None (deleted problems), and auto-deletes editorials when problems are deleted in the future.

Tested by adding an editorial to the Hello World problem in the demo, deleting the Hello World problem, verifying in the shell that the editorial still exists in the current DMOJ:master@head commit. Then applied the patch, ran python3 manage.py migrate, verified that the editorial no longer exists in the shell. Then recreated the instance but with the patch from the beginning this time, and verified that the editorial was deleted when the problem was deleted.

Closes #1958

@jdabtieu jdabtieu marked this pull request as ready for review October 4, 2022 16:50
@codecov-commenter
Copy link

codecov-commenter commented Oct 4, 2022

Codecov Report

Base: 46.50% // Head: 46.53% // Increases project coverage by +0.03% 🎉

Coverage data is based on head (81fb877) compared to base (932cbf1).
Patch coverage: 100.00% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2004      +/-   ##
==========================================
+ Coverage   46.50%   46.53%   +0.03%     
==========================================
  Files         236      237       +1     
  Lines       13088    13096       +8     
==========================================
+ Hits         6086     6094       +8     
  Misses       7002     7002              
Impacted Files Coverage Δ
judge/migrations/0133_remove_zombie_editorials.py 100.00% <100.00%> (ø)
judge/models/problem.py 89.26% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

Copy link
Contributor

@Riolku Riolku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks fine. Why do we have SET_NULL?

This is not rhetorical: we should check the git history.

@Riolku
Copy link
Contributor

Riolku commented Oct 4, 2022

It originates here.

Unrelated, but we could add a unit test for this.

@jdabtieu
Copy link
Contributor Author

jdabtieu commented Oct 4, 2022

Looks like there's also some weird witchcraftery here: 9c1208f

Assuming obj is a Solution object, obj.problem should never have been allowed to be None

@Xyene
Copy link
Member

Xyene commented Oct 31, 2022

@quantum5, do you remember why we made this SET_NULL in the first place?

@quantum5
Copy link
Member

In the ancient times, we sometimes created a single editorial page for a whole contest.

@jdabtieu
Copy link
Contributor Author

Do those editorials still exist? If so, may be worth dumping them to some file before wiping them.

@Xyene
Copy link
Member

Xyene commented Nov 1, 2022

In [0]: Solution.objects.filter(problem__isnull=True).values_list('id', 'is_public', 'authors__user__username', 'content')
[
  [
    453,
    true,
    "Relativity",
    "To begin with, a 2d matrix would be very viable in this situation. Looking at the problem from a different perspective we can notice that there simply isn't a need to store every test score, instead store the average within an array sized ```Num of Students``` by ```2```.\r\n\r\n```java\r\nfor (int i = 0; i < s; i++) {\r\n\tint studentNum = readInt();\r\n\tgrid[i][0] = studentNum;\r\n\tint average = 0;\r\n\tfor (int j = 1; j < t + 1; j++) {\r\n\t\tint testScore = readInt();\r\n\t\taverage += testScore;\r\n\t}\r\n\tgrid[i][1] = average / t;\r\n}\r\n```\r\n\r\nAfterwards simply find the max and min of each student."
  ],
  [
    453,
    true,
    "SongJiAh",
    "To begin with, a 2d matrix would be very viable in this situation. Looking at the problem from a different perspective we can notice that there simply isn't a need to store every test score, instead store the average within an array sized ```Num of Students``` by ```2```.\r\n\r\n```java\r\nfor (int i = 0; i < s; i++) {\r\n\tint studentNum = readInt();\r\n\tgrid[i][0] = studentNum;\r\n\tint average = 0;\r\n\tfor (int j = 1; j < t + 1; j++) {\r\n\t\tint testScore = readInt();\r\n\t\taverage += testScore;\r\n\t}\r\n\tgrid[i][1] = average / t;\r\n}\r\n```\r\n\r\nAfterwards simply find the max and min of each student."
  ],
  [
    454,
    true,
    "Rimuru",
    "The two racers each start at their respective nodes, ~P~ and ~S~. We can have two arrays storing each person's distance to every single node. After, the easiest way to solve this problem is to run ~BFS~ twice, keeping the distance from node ~P~ in the first distance array, and the distance from node ~S~ in the second distance array.\r\n\r\nTo check whether [user:phantasm] beats [user:SongJiAh], simply compare the distances at node ~T~.\r\n\r\n```java\r\nimport java.util.Arrays;\r\nimport java.util.LinkedList;\r\nimport java.util.Scanner;\r\n\r\npublic class Main {\r\n\tpublic static void main(String[] args) {\r\n\t\t\r\n\t\tScanner sc = new Scanner(System.in);\r\n\t\t\r\n\t\tint N = sc.nextInt(), M = sc.nextInt();\r\n\t\tint P = sc.nextInt(), S = sc.nextInt();\r\n\t\tint PhantasmDis[] = new int[N+1];\r\n\t\tint SongjiahDis[] = new int[N+1];\r\n\t\tboolean vis[] = new boolean[N+1];\r\n\t\tboolean matrix[][] = new boolean[N+1][N+1];\r\n\t\t\r\n\t\tfor(int i=0; i<M; i++) {\r\n\t\t\tint a = sc.nextInt(), b = sc.nextInt();\r\n\t\t\tmatrix[a][b] = true;\r\n\t\t\tmatrix[b][a] = true;\r\n\t\t}\r\n\t\tint T = sc.nextInt();\r\n\t\t\r\n\t\tLinkedList<Integer> Q = new LinkedList<Integer>();\r\n\t\t\r\n\t\tQ.add(P);\r\n\t\tvis[P] = true;\r\n\t\tPhantasmDis[P] = 0;\r\n\t\twhile(!Q.isEmpty()) {\r\n\t\t\tint cur = Q.poll();\r\n\t\t\tfor(int i=1; i<=N; i++) {\r\n\t\t\t\tif(matrix[cur][i] && !vis[i]) {\r\n\t\t\t\t\tQ.add(i);\r\n\t\t\t\t\tvis[i] = true;\r\n\t\t\t\t\tPhantasmDis[i] = PhantasmDis[cur] + 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tArrays.fill(vis, false);\r\n\t\t\r\n\t\tQ.add(S);\r\n\t\tvis[S] = true;\r\n\t\tSongjiahDis[S] = 0;\r\n\t\twhile(!Q.isEmpty()) {\r\n\t\t\tint cur = Q.poll();\r\n\t\t\tfor(int i=1; i<=N; i++) {\r\n\t\t\t\tif(matrix[cur][i] && !vis[i]) {\r\n\t\t\t\t\tQ.add(i);\r\n\t\t\t\t\tvis[i] = true;\r\n\t\t\t\t\tSongjiahDis[i] = SongjiahDis[cur] + 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif(PhantasmDis[T]<SongjiahDis[T]) {\r\n\t\t\tSystem.out.println(\"yes! :)\");\r\n\t\t} else if(PhantasmDis[T]>SongjiahDis[T]) {\r\n\t\t\tSystem.out.println(\"no! :(\");\r\n\t\t} else {\r\n\t\t\tSystem.out.println(\"meh :|\");\r\n\t\t}\r\n\t\t\r\n\t}\r\n\t\t\r\n}```"
  ],
  [
    455,
    true,
    "Rimuru",
    "If you didn't read the problem, then you would have noticed your submissions being **rejudged** (possibly receiving a hidden message that replaces your source code), receiving a **score of 0** if you did not use a recursive method.\r\n\r\nSimilar to the *fibonnaci* code, where: $$n! = n\\cdot (n-1)!$$\r\n\r\nWe are able to notice that for this, to calculate: $$\\sum_{i=1}^n$$\r\n$$\\sum_{i=1}^n = n + \\sum_{i=1}^{n-1}$$\r\n\r\nWe implement this recursively, like we do with factorial.\r\n\r\n```java\r\nimport java.util.Scanner;\r\n\r\npublic class test {\r\n\tpublic static void main(String[] args) {\r\n\t\t\r\n\t\tScanner sc = new Scanner(System.in);\r\n\t\t\r\n\t\tint N = sc.nextInt();\r\n\t\tSystem.out.println(add(N));\r\n\t\r\n\t}\r\n\t\r\n\tstatic int add(int n) {\r\n\t\tif(n==1) return 1;\r\n\t\treturn n + add(n-1);\r\n\t}\r\n\t\t\r\n}\r\n```"
  ],
  [
    456,
    true,
    "Rimuru",
    "## Why is sleep important?\r\n\r\nWell you see, if you try to make a problem or generate data late at night, you find out that does not work out very well...\r\n\r\nEspecially for [user:ss__jonathan] accidentally deleted a `.` from the testcase, angering the one and only [user:SlowestLoris].\r\n\r\nHehe, [user:phantasm] thought you could move diagonally, but I explicitly stated that you could only move *up, down, left or right*...\r\n\r\n![He Wa'd. :)][1]\r\n\r\n## Editorial\r\n\r\nAnyways, back to the question. I had intended to make this question much simpler, such that doing BFS from the starting point to the end node, and doing BFS for all of the love potions combined would suffice. In the end, comparing the minimum distance for the love potion to the starting node was the intended question.\r\n\r\nApparently not.\r\n\r\nIf you are *somehow* cut off by the liquids of the love potion before you reach the door, would you be able to enter it? *I think not, my friend!*\r\nTherefore, you would have to make sure that when you BFS from the starting point, it is **less than or equal** to the distances of the love potions.\r\n\r\nAnother thing you should look out for is if you can't reach the door at all. (I know, I know, how did he even get in the room if there's no accessible door?)\r\n\r\nIf you were able to do this in contest, congrats!\r\n\r\nEither way, test data was weak enough that just comparing the end nodes would have sufficed. Thanks for reading all of my hard work. \ud83d\ude42\r\n\r\n\r\n  [1]: https://image.ibb.co/cfGTwU/unknown.png"
  ],
  [
    457,
    true,
    "Relativity",
    "## Editorial:\r\nThe problem asks for the minimum amount of steps [user:Pusheen] needs to visit the apple tree before going to [user:ss__jonathan]'s house. Knowing this we can use a simple BFS to find the minimum distance from his starting position to the apple tree, and from that we can use another BFS using the apple tree as the starting position this time to get the minimum distance to [user:ss__jonathan]'s house. \r\n\r\nJava: From starting position to apple tree\r\n```java\r\nQueue<Integer> rowQ = new LinkedList<Integer>();\r\n\t\tQueue<Integer> colQ = new LinkedList<Integer>();\r\n\t\trowQ.add(rowP);\r\n\t\tcolQ.add(colP);\r\n\t\tstep[rowP][colP] = 0;\r\n\t\twhile (!rowQ.isEmpty()) {\r\n\t\t\tint curR = rowQ.poll();\r\n\t\t\tint curC = colQ.poll();\r\n\t\t\tif (curR - 1 >= 0 && step[curR - 1][curC] > step[curR][curC] + 1 && grid[curR - 1][curC] > 0) {\r\n\t\t\t\tstep[curR - 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR - 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curR + 1 < n && step[curR + 1][curC] > step[curR][curC] + 1 && grid[curR + 1][curC] > 0) {\r\n\t\t\t\tstep[curR + 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR + 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curC - 1 >= 0 && step[curR][curC - 1] > step[curR][curC] + 1 && grid[curR][curC - 1] > 0) {\r\n\t\t\t\tstep[curR][curC - 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC - 1);\r\n\t\t\t}\r\n\t\t\tif (curC + 1 < m && step[curR][curC + 1] > step[curR][curC] + 1 && grid[curR][curC + 1] > 0) {\r\n\t\t\t\tstep[curR][curC + 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC + 1);\r\n\t\t\t}\r\n\t\t}\r\n```\r\nJava: From apple tree to [user:ss__jonathan]'s house\r\n```java\r\nint d1 = step[rowA][colA];\r\n\t\tfor (int i = 0; i < step.length; i++) {\r\n\t\t\tArrays.fill(step[i], 1 << 30);\r\n\t\t}\r\n\t\trowQ = new LinkedList<Integer>();\r\n\t\tcolQ = new LinkedList<Integer>();\r\n\t\trowQ.add(rowA);\r\n\t\tcolQ.add(colA);\r\n\t\tstep[rowA][colA] = 0;\r\n\t\twhile (!rowQ.isEmpty()) {\r\n\t\t\tint curR = rowQ.poll();\r\n\t\t\tint curC = colQ.poll();\r\n\t\t\tif (curR - 1 >= 0 && step[curR - 1][curC] > step[curR][curC] + 1 && grid[curR - 1][curC] > 0) {\r\n\t\t\t\tstep[curR - 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR - 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curR + 1 < n && step[curR + 1][curC] > step[curR][curC] + 1 && grid[curR + 1][curC] > 0) {\r\n\t\t\t\tstep[curR + 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR + 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curC - 1 >= 0 && step[curR][curC - 1] > step[curR][curC] + 1 && grid[curR][curC - 1] > 0) {\r\n\t\t\t\tstep[curR][curC - 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC - 1);\r\n\t\t\t}\r\n\t\t\tif (curC + 1 < m && step[curR][curC + 1] > step[curR][curC] + 1 && grid[curR][curC + 1] > 0) {\r\n\t\t\t\tstep[curR][curC + 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC + 1);\r\n\t\t\t}\r\n\t\t}\r\n```\r\n\r\nWe can also create 2 separate variables such as ```int dist1``` and ```int dist2``` to keep track of the distance travelled from [user:Pusheen]'s house to the apple tree and then from the apple tree to [user:ss_jonathan]'s house.\r\n\r\nJava: Adding distances and checking if it's possible to reach apple tree and [user:ss__jonathan]'s house\r\n```java\r\nint d2 = step[rowJ][colJ];\r\n\t\tif (d2 == 1 << 30 || d1 == 1 << 30) {\r\n\t\t\tSystem.out.println(-1);\r\n\t\t}\r\n\t\telse {\r\n\t\t\tSystem.out.println(d1 + d2);\r\n\t\t}\r\n```"
  ],
  [
    457,
    true,
    "SongJiAh",
    "## Editorial:\r\nThe problem asks for the minimum amount of steps [user:Pusheen] needs to visit the apple tree before going to [user:ss__jonathan]'s house. Knowing this we can use a simple BFS to find the minimum distance from his starting position to the apple tree, and from that we can use another BFS using the apple tree as the starting position this time to get the minimum distance to [user:ss__jonathan]'s house. \r\n\r\nJava: From starting position to apple tree\r\n```java\r\nQueue<Integer> rowQ = new LinkedList<Integer>();\r\n\t\tQueue<Integer> colQ = new LinkedList<Integer>();\r\n\t\trowQ.add(rowP);\r\n\t\tcolQ.add(colP);\r\n\t\tstep[rowP][colP] = 0;\r\n\t\twhile (!rowQ.isEmpty()) {\r\n\t\t\tint curR = rowQ.poll();\r\n\t\t\tint curC = colQ.poll();\r\n\t\t\tif (curR - 1 >= 0 && step[curR - 1][curC] > step[curR][curC] + 1 && grid[curR - 1][curC] > 0) {\r\n\t\t\t\tstep[curR - 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR - 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curR + 1 < n && step[curR + 1][curC] > step[curR][curC] + 1 && grid[curR + 1][curC] > 0) {\r\n\t\t\t\tstep[curR + 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR + 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curC - 1 >= 0 && step[curR][curC - 1] > step[curR][curC] + 1 && grid[curR][curC - 1] > 0) {\r\n\t\t\t\tstep[curR][curC - 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC - 1);\r\n\t\t\t}\r\n\t\t\tif (curC + 1 < m && step[curR][curC + 1] > step[curR][curC] + 1 && grid[curR][curC + 1] > 0) {\r\n\t\t\t\tstep[curR][curC + 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC + 1);\r\n\t\t\t}\r\n\t\t}\r\n```\r\nJava: From apple tree to [user:ss__jonathan]'s house\r\n```java\r\nint d1 = step[rowA][colA];\r\n\t\tfor (int i = 0; i < step.length; i++) {\r\n\t\t\tArrays.fill(step[i], 1 << 30);\r\n\t\t}\r\n\t\trowQ = new LinkedList<Integer>();\r\n\t\tcolQ = new LinkedList<Integer>();\r\n\t\trowQ.add(rowA);\r\n\t\tcolQ.add(colA);\r\n\t\tstep[rowA][colA] = 0;\r\n\t\twhile (!rowQ.isEmpty()) {\r\n\t\t\tint curR = rowQ.poll();\r\n\t\t\tint curC = colQ.poll();\r\n\t\t\tif (curR - 1 >= 0 && step[curR - 1][curC] > step[curR][curC] + 1 && grid[curR - 1][curC] > 0) {\r\n\t\t\t\tstep[curR - 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR - 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curR + 1 < n && step[curR + 1][curC] > step[curR][curC] + 1 && grid[curR + 1][curC] > 0) {\r\n\t\t\t\tstep[curR + 1][curC] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR + 1);\r\n\t\t\t\tcolQ.add(curC);\r\n\t\t\t}\r\n\t\t\tif (curC - 1 >= 0 && step[curR][curC - 1] > step[curR][curC] + 1 && grid[curR][curC - 1] > 0) {\r\n\t\t\t\tstep[curR][curC - 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC - 1);\r\n\t\t\t}\r\n\t\t\tif (curC + 1 < m && step[curR][curC + 1] > step[curR][curC] + 1 && grid[curR][curC + 1] > 0) {\r\n\t\t\t\tstep[curR][curC + 1] = step[curR][curC] + 1;\r\n\t\t\t\trowQ.add(curR);\r\n\t\t\t\tcolQ.add(curC + 1);\r\n\t\t\t}\r\n\t\t}\r\n```\r\n\r\nWe can also create 2 separate variables such as ```int dist1``` and ```int dist2``` to keep track of the distance travelled from [user:Pusheen]'s house to the apple tree and then from the apple tree to [user:ss_jonathan]'s house.\r\n\r\nJava: Adding distances and checking if it's possible to reach apple tree and [user:ss__jonathan]'s house\r\n```java\r\nint d2 = step[rowJ][colJ];\r\n\t\tif (d2 == 1 << 30 || d1 == 1 << 30) {\r\n\t\t\tSystem.out.println(-1);\r\n\t\t}\r\n\t\telse {\r\n\t\t\tSystem.out.println(d1 + d2);\r\n\t\t}\r\n```"
  ],
  [
    532,
    true,
    "Rimuru",
    "If ~N \\ge 50~, output `don't charge`.\r\n\r\nOtherwise, output `charge`."
  ],
  [
    533,
    true,
    "Rimuru",
    "A valid solution to this problem is to simply brute force this problem. \r\n\r\nWe store a variable `current` (initially equal to ~N~), representing the number we are checking. From there, we loop through set ~d~ to check if `current` contains a digit that exists in ~d~. If the digit exists in ~d~, then we increment `current` by ~1~.\r\n\r\n<hr>\r\n\r\nAn alternative method to solving this problem (which is also the original intended solution) is to notice that ~d~ will always be sorted, and therefore we can binary search if an integer exists in the set ~d~."
  ],
  [
    534,
    true,
    "Rimuru",
    "The first approach to this question will probably be to loop from ~1~ to ~N~, and constantly checking for each number if it is an *Awkward Number* (this can be done by brute force). Unfortunately, the problem is that ~N~ can go up to ~1 000 000 000~ (1 billion)! In the worst case, this will take 1 billion iterations, and given that we only have 1 second as the time limit, this is not very good.\r\n\r\n\r\nTherefore, we can take another direct approach. Instead of checking each, individual number, we can generate all possible numbers that are less than or equal to ~N~ which are *awkward numbers*. For example... TO BE CONTINUED"
  ],
  [
    611,
    true,
    null,
    "Let ~N~ be the number of animals, ~M~ the size of the board and ~D~ the largest distance.\r\n\r\n## Solution for 1D board\r\n\r\nTo solve the 1D board, we first sort the coordinates and then perform a sweep-line algorithm. We keep track of\r\ntwo pointers: **head** and **tail**. When the head is pointing to an element with coordinate ~x~, the tail is pointing to\r\nthe first element with coordinate greater than or equal to ~x\u2212D~.\r\n\r\nFor each position of head we add head\u2212tail to the total result. As we advance head to the next element, we can\r\neasily adjust tail to point to the required element.\r\n\r\nThe time complexity of this algorithm is ~O(N log N)~ for sorting, and ~O(N)~ for sweeping.\r\n\r\n## Solution for 2D board\r\nTo solve the 2D version of the problem, we first consider the distance formula:\r\n\r\n$$dist(P, Q) = | P_x \u2212 Q_x | + | P_y \u2212 Q_y |$$\r\n\r\nThe formula above will resolve to one of the following four formulas:\r\n\r\n$$dist(P, Q) = P_x \u2212 Q_x + P_y \u2212 Q_y = (P_x + P_y) \u2212 (Q_x + Q_y)$$\r\n\r\n$$dist(P, Q) = P_x \u2212 Q_x \u2212 P_y + Q_y = (P_x \u2212 P_y) \u2212 (Q_x \u2212 Q_y)$$\r\n\r\n$$dist(P, Q) = \u2212P_x + Q_x + P_y \u2212 Q_y = (Q_x \u2212 Q_y) \u2212 (P_x \u2212 P_y)$$\r\n\r\n$$dist(P, Q) = \u2212P_x + Q_x \u2212 P_y + Q_y = (Q_x + Q_y) \u2212 (P_x + P_y)$$\r\n\r\nIt is easy to see that the distance is always equal to the largest of these four values. As ~P_x + P_y~ and ~P_x \u2212 P_y~\r\nrepresent the \"diagonal coordinates\" of point ~P~ we substitute:\r\n\r\n~P_{d1} := P_x + P_y~ and ~P_{d2} := P_x \u2212 P_y~.\r\n\r\nNow, we can rewrite the distance formula in terms of ~d1~ and ~d2~:\r\n\r\n~dist(P, Q) = max(P_{d1} \u2212 Q_{d1}, P_{d2} \u2212 Q_{d2}, Q_{d2} \u2212 P_{d2}, Q_{d1} \u2212 P_{d1})~,\r\n\r\nor shorter:\r\n\r\n~dist(P, Q) = max(|P_{d1} \u2212 Q_{d1}|, |P_{d2} \u2212 Q_{d2}|)~\r\n\r\nAfter substitution, we sort all the points by the first coordinate ~(d1)~ increasingly and perform a sweep-line\r\nalgorithm similar to the one-dimensional case. Since each point ~P~ between head and tail satisfies the inequality\r\n~head_{d1} \u2212 P_{d1} \u2264 D~, we only need to find out for how many of them the inequality ~|head_{d2} \u2212 P_{d2}| \u2264 D~ is\r\nsatisfied as well. To calculate that value, we keep all points (their d2 coordinates) between head and tail in either\r\nan **interval tree** or a **binary indexed tree** [1] data structure.\r\n\r\nThe time complexity of the algorithm implemented with a binary indexed tree data structure is ~O(N log N)~ for\r\nsorting, and ~O(N log M)~ for sweeping, where ~M~ is the upper bound on the coordinates.\r\n\r\n## Solution for 3D board\r\n\r\nInspired by our 2D solution we start with the distance formula once again and obtain:\r\n\r\n $$dist(P, Q) = max( |P_{f1} \u2212 Q_{f1}|, |P_{f2} \u2212 Q_{f2}|, |P_{f3} \u2212 Q_{f3}|, |P_{f4} \u2212 Q_{f4}| ),where $$\r\n $$P_{f1} := P_x + P_y + P_z$$\r\n $$P_{f2} := P_x + P_y \u2212 P_z$$\r\n $$P_{f3} := P_x \u2212 P_y + P_z$$\r\n $$P_{f4} := P_x \u2212 P_y \u2212 P_z$$\r\nAgain, we perform a sweep-line algorithm on the ~f1~ coordinate while keeping all of the points between **head** and\r\n**tail** in a 3D binary indexed tree in order to count the number of points ~P~ satisfying inequalities\r\n~|head_{f2} \u2212 P_{f2}| \u2264 D~, ~|head_{f3} \u2212 P_{f3}| \u2264 D~ and ~|head_{f4} \u2212 P_{f4}| \u2264 D~.\r\n\r\nThe time complexity of the algorithm is ~O(N log N)~ for sorting, and~ O(N log^{3}M)~ for sweeping.\r\n\r\nIt is worth mentioning that we can use this solution to solve all types of boards. We just assign any constant\r\nvalue (1 for example) to each of the missing coordinates and implement a 3D binary indexed tree using either\r\ndynamic memory allocation, or using a one-dimensional array and manually mapping the 3-dimensional space to\r\nelements of the array."
  ],
  [
    612,
    true,
    null,
    "Detecting an odd cycle in a graph is a well-known problem. A graph does not contain an odd cycle if and only if\r\nit is bipartite. On the other hand, the problem of detecting an even cycle in a graph is not widely known.\r\n\r\nWe are given a graph consisting of ~N~ vertices and ~M~ edges. Exactly ~N\u22121~ edges are marked as **tree edges** and\r\nthey form a tree. An edge that is not a tree edge will be called a **non-tree edge**. Every non-tree edge e has a\r\nweight ~w(e)~ associated with it.\r\n\r\nThe task asks us to find a minimum-weighted set of non-tree edges whose removal results in a graph that does\r\nnot contain a cycle of even length. We will call such a cycle an **even cycle**. Reasoning backwards, starting from a\r\ngraph containing tree edges only, we have to find a maximum-weighted set of non-tree edges that can be added\r\nto the graph without forming any even cycles.\r\nIn order to describe the model solution, we first need to make a few observations about the structure of the\r\ngraph we are working with.\r\n\r\n## Even and odd edges\r\n\r\nConsider a non-tree edge ~e=(A, B)~. We define the **tree path of the edge** ~e~ to be the unique path from ~A~ to ~B~\r\nconsisting of tree edges only. If the length of the tree path is even, we say that ~e~ is an **even edge**; otherwise we\r\nsay that ~e~ is an **odd edge**. We will use ~TP(e)~ to denote the tree path of an edge ~e~.\r\n\r\nObviously, any odd edge present in the graph together with its tree path forms an even cycle. Therefore, we can\r\nnever include an odd edge in our graph and we can completely ignore them.\r\n\r\n## Relation between two even edges\r\n\r\nIndividual even edges may exist in the graph. However, if we include several even edges, an even cycle might be\r\nformed. More precisely, if ~e_1~ and ~e_2~ are even edges such that ~TP(e_1)~ and ~TP(e_2)~ share a common tree edge, then\r\nadding both ~e_1~ and ~e_2~ to the graph necessarily creates an even cycle.\r\n\r\nIn order to sketch the proof of this claim, consider the two odd cycles created by ~e_1~ and ~e_2~ together with their\r\nrespective tree paths. If we remove all common tree edges from those cycles we get two paths ~P_1~ and ~P_2~. The\r\nparity of ~P_1~ is equal to the parity of the ~P_2~ since we removed the same number of edges from the two initial odd\r\ncycles. As ~P_1~ and ~P_2~ also have the same endpoints, we can merge them into one big even cycle.\r\n\r\n## Tree edges contained in odd cycles\r\n\r\nAs a direct consequence of the previous claim, we can conclude that **every tree edge** may be contained in **at\r\nmost one odd cycle**.\r\n\r\nConversely, if we add only even edges to the tree in such a way that every tree edge is contained in at most one\r\nodd cycle, then we couldn\u2019t have formed any even cycles. We briefly sketch the proof of this claim here. If an\r\neven cycle existed, it would have to contain one or more non-tree edges. Informally, if it contains exactly one\r\nnon-tree edge we have a contradiction with the assumption that only even edges are added; if it contains two or\r\nmore non-tree edges then we will arrive at a contradiction with the second assumption.\r\n\r\n## Model solution\r\n\r\nNow, we can use our observations to develop a dynamic programming solution for the problem. A **state** is a\r\nsubtree of the given tree. For each state we calculate the weight of the maximum-weighted set of even edges that\r\ncan be added to the subtree while maintaining the property that each tree edge is contained in at most one odd\r\ncycle. The solution for the task is the weight associated with the state representing the initial tree. \r\n\r\nTo obtain a recursive relation, we consider all even edges with tree paths passing through the root of the tree.\r\nWe can choose to do one of the following:\r\n\r\n(1) We do not add any even edge whose tree path passes through the root of the tree. In this case, we can\r\ndelete the root and proceed to calculate the optimal solution for each of the subtrees obtained after\r\ndeleting the root node.\r\n\r\n(2) We choose an even edge ~e~ whose tree path passes through the root of the tree and add it to the tree.\r\nNext, we delete all tree edges along ~TP(e)~ (since, now, they are contained in one odd cycle), and, finally,\r\nwe proceed to calculate the optimal solution for each of the subtrees obtained after deleting the tree\r\npath. Add ~w(e)~ to the total sum.\r\n\r\nWe will use the tree in figure 1 as an example. Figure 2 shows case (1) in the recursive relation (we choose not to\r\ninclude an edge whose tree path passes through the root). Figure 3 shows case (2) in the recursive relation, when\r\nwe include the even edge ~e=(7, 9)~ in the graph. \r\n\r\nBecause of the way the trees are decomposed, all subtrees that appear as subproblems can be represented with an\r\ninteger and a bit mask. The integer represents the index of the subtree's root node, while the bit mask represents\r\nwhich of the root node's children are removed from the subtree.\r\n\r\nThe total number of possible states is, therefore, bounded by ~N\u00b72^K~ where ~K~ is the maximum degree of a node.\r\nDepending on the implementation details, the time complexity of the algorithm can vary. The official\r\nimplementation has time complexity ~O(M log M + MN + M\u00b72^K)~."
  ]
]

@Xyene
Copy link
Member

Xyene commented Nov 1, 2022

In the ancient times, we sometimes created a single editorial page for a whole contest.

@quantum5 I think these editorials are just unreachable, we have no URL patterns that would expose them...

@int-y1
Copy link
Contributor

int-y1 commented Nov 1, 2022

All of these editorials can be safely deleted. A few fun facts:

@quantum5
Copy link
Member

quantum5 commented Nov 1, 2022

In the ancient times, we sometimes created a single editorial page for a whole contest.

@quantum5 I think these editorials are just unreachable, we have no URL patterns that would expose them...

It's probably lost with we overhauled the editorial paths...

@Xyene
Copy link
Member

Xyene commented Jan 2, 2023

This LGTM, @jdabtieu could you please rebase your migration? Then we can merge this.

@jdabtieu
Copy link
Contributor Author

jdabtieu commented Jan 4, 2023

Rebased @Xyene

@Xyene
Copy link
Member

Xyene commented Jan 4, 2023

Code LGTM, could you please:

  • interactive rebase to squash your commits,
  • start your commit titles with a capital, and
  • move some of the context in your PR body (especially the Closes line) into the commit text?

After that we should be good to merge.

Closes #1958
- Delete old editorials not associated with a problem (problem=None)
- Change behaviour of editorials to be deleted when its associated problem is also deleted
Copy link
Member

@Xyene Xyene left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@Xyene Xyene merged commit 930b3d7 into DMOJ:master Jan 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Solution exists, even after its problem is deleted
6 participants