# DDJ 出題教學 因為我相信有很多人都解到$18\%$了然後想出題 所以就寫了這個神奇東西 這裡分成六個部分講: 1. 出題目本身 2. 把題目出到DDJ上 3. 如何用程式生測資 4. 用$\LaTeX$讓題目更加美觀 5. $Special\ Judge$寫法 6. 其他 ## 出題目本身 1. 首先,你要有一個想要出的**概念、算法**,例:二分搜、$DP$ 當你有了想要出的東西,你要有**範例的輸入輸出**,讓大家知道你要他們做的事情是甚麼 2. 再來,你要用一個故事去包裝他,例:[a849](http://203.64.191.163/ShowProblem?problemid=a849)、或是直接懶得想題目然後出裸題,例:[a868](http://203.64.191.163/ShowProblem?problemid=a868) 不管是哪個,你都要**明確的說明所有會用到的變數和其範圍**,不要有模稜兩可的東西。 3. 最後,你要生全部的側資,可以手打(不建議)或是**用程式生測資** 要怎麼用程式生下面會講 反正這些弄完你就有**題目的本身**了,剩下就是要丟到$DDJ$上了 :::danger **注意,不要出的題目種類:** * 水題,或是隨便喇就能過得題目,因為一堆基礎與法相關的題目之前都出過了,例:[a616](http://203.64.191.163/ShowProblem?problemid=a616),不然不管是誰躺著都能拿到出題資格,然後繼續這個惡性循換 * 大爛通靈題,例:[a799](http://203.64.191.163/ShowProblem?problemid=a799)、[a796](http://203.64.191.163/ShowProblem?problemid=a796),就是只能猜的那種大爛題,這裡是解題網站、不是猜題網站 * 霸凌少數人的題目,例:出高三程度數學題然後不丟公式,~~霸凌多數人可以~~ * 出過太多次的題目,例:**純的**費波那契數列 當然如果要出教學類的題目,出水題就滿必要的,不要太水就好 ::: ## 把題目出到DDJ上 相信大家是可以想出一個**不是爛的的題目** 那剩下的就是出到$DDJ$上了 --- ![](https://i.imgur.com/uXDZDoF.png =75%x) --- 有$18\%$後點這裡就可以拿到出題資格 --- ![](https://i.imgur.com/uAcY4j5.png =50%x)) --- ![](https://i.imgur.com/kvbHH4D.png =75%x)) --- 進去之後就會分成好幾個區域 主要可以分成**四大區域:左、右上、右中、右下** ### 左邊 依序會看到的是: * 標題:就是打標題 * 內容:主要提敘 * 輸入說明:輸入說明 * 輸出說明:輸出說明 * 範例輸入:範例輸入 * 範例輸出:範例輸出 * 提示:把**題目變數的一些範圍**打在這邊 * 參考解法:只有自己看的到,右邊上面某個東西會用到這個 * 備註:只有自己看的到,就是寫給自己的備註 * 儲存題目內容:**儲存題目內容,很重要,記得儲存** 左邊的部分你只要把題目打進去就可以了,很簡單 之後可以把一些**數學式子用$\LaTeX$變得更漂亮**(下面會教) ### 右上 --- ![](https://i.imgur.com/8qHmcot.png) --- 最上面方形區域(用的到的我才講): * $Problem\ Setting$(齒輪):呼叫右上角的東西出來 * 編輯題目(紙筆):呼叫整個頁面 * 參考程式碼(</>):就是剛剛左邊下面你丟的東西 * $rejudge$ 本題目全部程式碼(圓箭頭):重判全部程式碼(點一次就可以了,他不會顯示有沒有跑完,只能自己到解題動態看) * 放棄題目(拉進垃圾車):放棄題目 * $Waiting$:在題目內部測試自己的程式碼(要在**參考解法**有丟東西才有用,然後在這裡似乎側不了有中文的輸出) 剩下的自己看應該很簡單 然後**記得要儲存設定** :::info **補充,如果要卡掉$Python$的程式碼:** 有些題目要卡掉$Python$的強大運算能力(大數、...) 可以把**記憶體限制**改成$16MBytes$ ::: ### 右中 **上傳測資的地方** 生測資的方法:手打 or **用程式生**(下面會教) 反正不管用哪個,測資的部分有幾個重要的地方 1. 格式:XXXX_YY.in, XXXX_YY.out, XXXX是題目編號(例:a196), YY是測資編號(從$00$開始數, $00,\ 01,\ 02\, \cdots$) 2. 時間限制:**不要卡太死**,所有測資點盡量湊**0.5的倍數**並且時間限制都一致,這樣比較好看,可以**先訂測資時限再生測資的大小**(一秒複雜度大概$3*10^8$個運算),不然時長會很奇怪(例:[a510](http://203.64.191.163/ShowProblem?problemid=a510)),如果時長卡在一個很尷尬的地方,可以在題目輸入時用一個$T$來當單側資點的側資數,來改變時長 3. 記得儲存:**記得儲存** ### 右下 寫$Special\ Judge$的地方 一般不會碰到,要**自己寫判斷程式時才會用到**(下面會教) 沒在用時就空著就好 ## 如何用程式生測資 簡單來說就是自己寫一個**隨機產生器、並套上自己的格式** 先來說隨機生成的部分 ```cpp srand(time(0)); // set random seed to time(0) 把生成亂數的種子設成目前時間 mt19937 mt(rand()); // mt19937(他是一種資料型態): 一個比純rand()還要好的亂數生成工具 int a = 0, b = 10; uniform_int_distribution<int> dis(a, b); // 平均的隨機生成[a, b]之間的數字 // 尖括號裡放型態,如果要其他種生成方式歡迎去google "c++ uniform distribution" cout << dis(mt) << endl; // 用mt當隨機的種子,生成隨機的[a, b]之間的數字 ``` 這些是生成隨機資料時主要會用到的東西 :::info **補充:** `srand(time(0))`建議放在`main`的第一行,因為之後呼叫的`rand()`需要在`srand`的後面,然後`srand(time(0))`宣告了一次過後就不用再寫一次了(反正就是他呼叫過後之後都會是以`time(0)`為生成種子) ::: 接下來是輸入輸出流的改變的部分 因為我們要生成的是**文字檔案**,所以要讀入的東西不會是`cin`,輸出的東西有不會是`cout` 所以我們要把讀入跟讀出的檔案替代掉標準輸入(`stdin`)跟標準輸出(`stdout`) 我們會用到`freopen(A, B, C)` 其中: A = 用來代替的檔案,資料型態為`char*`,所以要在原本的字串外面包一層`("...").c_str()`才能傳進去 B = 讀入(`r`) 或 寫入(`w`) C = 要代替掉的檔案 其中的所有組合中我們會用到的只有三個: ```cpp= freopen(("XXXX_YY.in").c_str(), "w", stdout); // 把stdout用XXXX_YY.in替代掉 // 這個是要把生成好的東西丟到輸入檔中 freopen(("XXXX_YY.in").c_str(), "r", stdin); // 把stdin用XXXX_YY.in替代掉 freopen(("XXXX_YY.out").c_str(), "w", stdout); // 把stdout用XXXX_YY.out替代掉 // 這兩個是要解題並生成輸出檔時要用的 ``` :::info `freopen(...)`**執行了一次過後**就之後都會是以那個檔案代替掉標準輸入輸出流,但因為有時候會生成多筆測資,所以可以用不同的檔案直接再宣告一次就可以更新成用新的檔案代替輸入輸出流 然後因為`stdout`被取代,所以如果要輸出一些訊息給自己看的話就要用`cerr`,用法跟`cout`一樣 如果想要`cin`或`cout`再次起作用,就用`freopen("CON", "r", stdin)`或`freopen("CON", "w", stdout)` 把`endl`換成`"\n"`會生得比較快 ::: :::success 最近發現一個更好用和直觀的方法,雖然很毒 直接建名為`cin`跟`cout`的`fstream`就好了 ```cpp= fstream cin(("XXXX_YY.in").c_str()), cout(("XXXX_YY.out").c_str()); ``` ::: 所以整體下來步驟為 1. 設定隨機生成的各種東西 2. 把stdout用XXXX_YY.in代替,並生成輸入檔 3. 把stdin用XXXX_YY.in代替、把stdout用XXXX_YY.out代替,解題的同時生成輸出檔 例: 如果以[a658](http://203.64.191.163/ShowProblem?problemid=a658)為生成範例 因為這題有三個側資點,所以`freopen`的更新要放在`for`裡一次更新三個不同組輸入輸出檔 可以這樣寫: ```cpp= #include<bits/stdc++.h> #include<random> #include<time.h> #define endl "\n" using namespace std; int main() { srand(time(0)); mt19937 mt(rand()); uniform_int_distribution<unsigned> dis(0, 4294967295); int t[3] = {1000, 100000, 1000000}; for(int i = 0; i < 3; i ++ ) { string in = "a658_0" + to_string(i) + ".in"; string out = "a658_0" + to_string(i) + ".out"; freopen(in.c_str(), "w", stdout); cout << t[i] << endl; while(t[i] -- ) cout << dis(mt) << " " << dis(mt) << endl; freopen(in.c_str(), "r", stdin); freopen(out.c_str(), "w", stdout); unsigned T, N, M; cin >> T; while(T -- ) { cin >> N >> M; cout << "0 0 0" << endl; } cerr << "ok" << endl; } } ``` ## 用$\LaTeX$讓題目更加美觀 在$DDJ$題目中看到的**橘橘的,很漂亮的字**,就是用$\LaTeX$寫的 整體就是**用`$`包住頭尾,然後裡面寫$\LaTeX$自己的語法** 整體可以用的語法可以看 https://hackmd.io/@RintarouTW/LaTeX_%E8%AA%9E%E6%B3%95%E7%AD%86%E8%A8%98 (這裡的應該都可以用到$DDJ$上) 跟 https://www.cnblogs.com/Coolxxx/p/5982439.html (這裡的大部分都可以) 整體要注意的東西有: 1. 左`$`的右邊&右`$`的左邊**不能有空格** 2. 如果要打出空格,要**用`\`後面加一的空格** 3. 如果要跨越多行: (1) 要在每一行結尾加上`\\`,讓他換行(最後一行除外) (2) 換行要用`Shift+Enter`,讓整個$\LaTeX$在同個`html`段落裡 4. 如果想要整個東西置中,就在改用`$$`包起來 差不多會長這樣: --- ![](https://i.imgur.com/vHmmWNA.png) --- :::info **建議:** **先在$hackmd$上打過一遍,確定可行再複製到$DDJ$上** **注意:** 在$DDJ$如果要讓他觸發$\LaTeX$ ![](https://i.imgur.com/kRcuqXl.png) 把$\LaTeX$選起來然後把全部的$Formatting$點掉($Headings$可以留) 如果還是不行就直接先貼到記事本裡再複製貼上回來 ::: :::success **神奇的$\LaTeX$優化:** 如果有人覺得橘色很醜 可以在全部的外面包上 ``` \color{#333333}{...} ``` 這裡的`#333333`是字體色號,可以自己改成喜歡的 ![](https://i.imgur.com/G971FrH.png) 這樣就是黑的 但因為每個$\LaTeX$都要打這麼多字才能包起來很麻煩 所以可以用$\LaTeX$中的$define$功能 ``` $\def\bk{\color{#333333}}$ ``` 可以在內容的第一行打上這個 在這以下的東西**都可以用`\bk`來代替`\color{#333333}`** 當然你也可以把`bk`改成其他名稱 雖然這樣會留下一個`.`的痕跡,但只要在同一行打字就幾乎可以蓋掉 但是又有一個問題,就是**右邊那一個豎槓,很醜** 所以有一個極為神奇的方法可以把它弄掉 這種方法**又麻煩又有很大的副作用** 使用前要三思 ![](https://i.imgur.com/khzeJow.png) ![](https://i.imgur.com/NRCa7Ic.png) 沒錯,要改$html$ 反正就是要把`$`包起來的地方 再**用`<font color=white>...</font>`包起來** ![](https://i.imgur.com/eBkwLZN.png) 變這樣,儲存之後你會發現**字都變白的** 對,這就是他**很大的副作用** ![](https://i.imgur.com/fc2CkFS.png) 但**那條線就消失了,漂亮多了** 然後如果真的要這樣手動改的話真的會改到死 所以我就直接丟程式碼: ```cpp= #include<bits/stdc++.h> #define IO cin.tie(0) -> sync_with_stdio(0) #define endl "\n" #define F first #define S second #define pLL pair<LL, LL> #define pb emplace_back #define pf emplace_front #define ppb pop_back #define ppf pop_front #define LL lolita #define lolita long long using namespace std; void init(); const LL mod = 1e9 + 7, inf = 9e18, maxN = 196608; LL arr[maxN], i = 0; bool bln = 0; string str, s1 = "<font color=white>", s2 = "</font>"; void one() { LL j = i + 1; while(j < str.length() && (str[j] != '$' || str[j - 1] == '\\')) j ++ ; if(j == str.length()) { bln = 1; return; } if(i >= s1.length() && j + s2.length() < str.length() && str.substr(i - s1.length(), s1.length()) == s1 && str.substr(j + 1, s2.length()) == s2) { i = j + s2.length() + 1; return; } str.insert(i, s1); str.insert(j + s1.length() + 1, s2); i = j + s1.length() + s2.length(); } void two() { LL j = i + 2; while(j + 1 < str.length() && (str[j] != '$' || str[j + 1] != '$')) j ++ ; if(j + 1 == str.length()) { bln = 1; return; } if(i >= s1.length() && j + 1 + s2.length() < str.length() && str.substr(i - s1.length(), s1.length()) == s1 && str.substr(j + 2, s2.length()) == s2) { i = j + s2.length() + 2; return; } str.insert(i, s1); str.insert(j + s1.length() + 2, s2); i = j + s1.length() + s2.length() + 1; } void solve() { init(); string tmp; cout << "Please enter your source code: " << endl << endl; while(getline(cin, tmp)) { if(tmp == "<EOF>") break; str += tmp + '\n'; } for(; i < str.length(); i ++ ) { if(str[i] != '$') continue; if(str[i + 1] == '$') two(); else one(); if(bln) break; } if(bln) { cout << "Hmmm... There's some wrong with your \'$\' of LaTeX" << endl; return; } cout << "Ok! Your code seems to have no problems. Here's your new source code: " << endl << endl; cout << str << endl; } int main() { // IO; solve(); } void init() { } ``` 使用方法: 1. 把$source\ code$丟進去 2. 在**新的一行**輸出`ctrl+z`讓`DOS`執行終止 3. 最後把輸出的$new\ source\ code$複製丟回去$source\ code$的地方 然後如果想把變白色的的東西變回來 ![](https://i.imgur.com/vsJ22P4.png) 全選後點這邊就可以了 所以**真的先在$hackmd$上寫好再弄上去是會好的方法** ::: ## $Special\ Judge$寫法 就是要自己寫評斷方式 上面有寫一個一個說明,但是範例是$python$的所以應該有很多人就放棄了 這裡先丟一個`c++`範例 ```cpp= #include<bits/stdc++.h> #define IO cin.tie(0) -> sync_with_stdio(0) #define endl "\n" #define F first #define S second #define pLL pair<LL, LL> #define pb push_back #define LL lolita #define lolita long long using namespace std; int main(int argc, char* argv[]) { fstream outfile(string(argv[2]).c_str(), ios::in), ansfile(string(argv[3]).c_str(), ios::in); double tmp; vector<double> out, ans; while(outfile >> tmp) out.pb(tmp); while(ansfile >> tmp) ans.pb(tmp); if(out.size() != 500000) { cout << "$JUDGE_RESULT=WA" << endl; cout << "$LINECOUNT=輸出行數錯誤" << endl; cout << "$MESSAGE=你共輸出了" << out.size() << "行" << endl; return 0; } for(LL i = 0; i < 500000; i ++ ) { if(fabs(out[i] - ans[i]) > 0.001) { cout << "$JUDGE_RESULT=WA" << endl; cout << "$LINECOUNT=輸出數字錯誤" << endl; cout << "$MESSAGE=你有可能是精度不夠,不然就是自己寫爛了" << endl; return 0; } } cout << "$JUDGE_RESULT=AC" << endl; } ``` 反正比較重要的就只有: 1. `argv[1]`是標準輸入檔,`argv[2]`是標準輸出檔,`argv[3]`是使用者輸出檔 2. 不能用`freopen`了,因為有超過一個要讀入的輸入流,直接宣告`fstream`會比較好(寫法跟用法就在上面,其他寫法歡迎`google`) 3. $Special\ Judge$的`c++`好像是`c++98`,代表不能用`for(auto &i : arr)`這種或其他東西 4. 要判定的方面有很多,例:輸出答案個數是否正確,數字型態是否正確...... **要寫的全面**才能防止有人鑽漏洞 5. 寫完記得要把右上角的地方調成$Special\ Judge$ 6. 如果用了$Special\ Judge$,題目的時限會算到$Special\ Judge$所用到的時間 喔對這題是[a836](http://203.64.191.163/ShowProblem?problemid=a836) :::info **補充:** $Special\ Judge$在正常情況下是看不到上傳者的程式碼本身的, 所有想要寫那種要看別人程式碼裡面東西的題目的可以死心了 然後$Special\ Judge$中用不到的檔案可以空著沒關係 ::: ## 其他 關於某些東西的模板 可以看這裡:https://hackmd.io/@chrislaiisme/templates 只是我生測資的模板應該滿難懂的,但好用 剩下就加油了,相信各位都可以出出優質的好題 (P.S. 現在是凌晨3點,我在吃美味蟹堡)