DDJ 出題教學

因為我相信有很多人都解到

18%了然後想出題,所以就寫了這個神奇東西

(來自2024/8/26的chrislaiisme : 聽說現在變成14%了)

這裡分成六個部分講:

  1. 出題目本身
  2. 把題目出到DDJ上
  3. 如何用程式生測資
  4. LATEX
    讓題目更加美觀
  5. Special Judge
    寫法
  6. 其他

出題目本身

  1. 首先,你要有一個想要出的概念、算法,例:二分搜、
    DP

    當你有了想要出的東西,你要有範例的輸入輸出,讓大家知道你要他們做的事情是甚麼
  2. 再來,你要用一個故事去包裝他,例:a849、或是直接懶得想題目然後出裸題,例:a868
    不管是哪個,你都要明確的說明所有會用到的變數和其範圍,不要有模稜兩可的東西。
  3. 最後,你要生全部的側資,可以手打(不建議)或是用程式生測資
    要怎麼用程式生下面會講

反正這些弄完你就有題目的本身了,剩下就是要丟到

DDJ上了

注意,不要出的題目種類:

  • 水題,或是隨便喇就能過得題目,因為一堆基礎與法相關的題目之前都出過了,例:a616,不然不管是誰躺著都能拿到出題資格,然後繼續這個惡性循換
  • 大爛通靈題,例:a799a796,就是只能猜的那種大爛題,這裡是解題網站、不是猜題網站
  • 霸凌少數人的題目,例:出高三程度數學題然後不丟公式,霸凌多數人可以
  • 出過太多次的題目,例:純的費波那契數列

當然如果要出教學類的題目,出水題就滿必要的,不要太水就好

把題目出到DDJ上

相信大家是可以想出一個不是爛的的題目
那剩下的就是出到

DDJ上了


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


18%後點這裡就可以拿到出題資格


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
)


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
)


進去之後就會分成好幾個區域
主要可以分成四大區域:左、右上、右中、右下

左邊

依序會看到的是:

  • 標題:就是打標題
  • 內容:主要提敘
  • 輸入說明:輸入說明
  • 輸出說明:輸出說明
  • 範例輸入:範例輸入
  • 範例輸出:範例輸出
  • 提示:把題目變數的一些範圍打在這邊
  • 參考解法:只有自己看的到,右邊上面某個東西會用到這個
  • 備註:只有自己看的到,就是寫給自己的備註
  • 儲存題目內容:儲存題目內容,很重要,記得儲存

左邊的部分你只要把題目打進去就可以了,很簡單
之後可以把一些數學式子用

LATEX變得更漂亮(下面會教)

右上


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


最上面方形區域(用的到的我才講):

  • Problem Setting
    (齒輪):呼叫右上角的東西出來
  • 編輯題目(紙筆):呼叫整個頁面
  • 參考程式碼(</>):就是剛剛左邊下面你丟的東西
  • rejudge
    本題目全部程式碼(圓箭頭):重判全部程式碼(點一次就可以了,他不會顯示有沒有跑完,只能自己到解題動態看)
  • 放棄題目(拉進垃圾車):放棄題目
  • Waiting
    :在題目內部測試自己的程式碼(要在參考解法有丟東西才有用,然後在這裡似乎側不了有中文的輸出)

剩下的自己看應該很簡單
然後記得要儲存設定

補充,如果要卡掉

Python的程式碼:
有些題目要卡掉
Python
的強大運算能力(大數、)
可以把記憶體限制改成
16MBytes

右中

上傳測資的地方
生測資的方法:手打 or 用程式生(下面會教)
反正不管用哪個,測資的部分有幾個重要的地方

  1. 格式:XXXX_YY.in, XXXX_YY.out, XXXX是題目編號(例:a196), YY是測資編號(從
    00
    開始數,
    00, 01, 02
    )
  2. 時間限制:不要卡太死,所有測資點盡量湊0.5的倍數並且時間限制都一致,這樣比較好看,可以先訂測資時限再生測資的大小(一秒複雜度大概
    3108
    個運算),不然時長會很奇怪(例:a510),如果時長卡在一個很尷尬的地方,可以在題目輸入時用一個
    T
    來當單側資點的側資數,來改變時長
  3. 記得儲存:記得儲存

右下

Special Judge的地方
一般不會碰到,要自己寫判斷程式時才會用到(下面會教)
沒在用時就空著就好

如何用程式生測資

簡單來說就是自己寫一個隨機產生器、並套上自己的格式

先來說隨機生成的部分

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]之間的數字

這些是生成隨機資料時主要會用到的東西

補充:
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 = 要代替掉的檔案

其中的所有組合中我們會用到的只有三個:

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替代掉 // 這兩個是要解題並生成輸出檔時要用的

freopen(...)執行了一次過後就之後都會是以那個檔案代替掉標準輸入輸出流,但因為有時候會生成多筆測資,所以可以用不同的檔案直接再宣告一次就可以更新成用新的檔案代替輸入輸出流

然後因為stdout被取代,所以如果要輸出一些訊息給自己看的話就要用cerr,用法跟cout一樣

如果想要cincout再次起作用,就用freopen("CON", "r", stdin)freopen("CON", "w", stdout)

endl換成"\n"會生得比較快

最近發現一個更好用和直觀的方法,雖然很毒
直接建名為cincoutfstream就好了

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為生成範例
因為這題有三個側資點,所以freopen的更新要放在for裡一次更新三個不同組輸入輸出檔
可以這樣寫:

#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_語法筆記 (這裡的應該都可以用到
DDJ
上)

https://www.cnblogs.com/Coolxxx/p/5982439.html (這裡的大部分都可以)

整體要注意的東西有:

  1. $的右邊&右$的左邊不能有空格
  2. 如果要打出空格,要\後面加一的空格
  3. 如果要跨越多行:
    (1) 要在每一行結尾加上\\,讓他換行(最後一行除外)
    (2) 換行要用Shift+Enter,讓整個
    LATEX
    在同個html段落裡
  4. 如果想要整個東西置中,就在改用$$包起來

差不多會長這樣:



建議:
先在

hackmd上打過一遍,確定可行再複製到
DDJ

注意:

DDJ如果要讓他觸發
LATEX

LATEX選起來然後把全部的
Formatting
點掉(
Headings
可以留)

如果還是不行就直接先貼到記事本裡再複製貼上回來

神奇的

LATEX優化:

(2024/8/11 補充:我發現一個很簡單把字變成黑色的方法
只要對LaTeX點右鍵之後點底下的東西

未命名
之後所有LaTeX都會變成沒有側線的圖片
也可以試試點其他選項
或是用我下面那有些複雜的方法)

如果有人覺得橘色很醜
可以在全部的外面包上

\color{#333333}{...}

這裡的#333333是字體色號,可以自己改成喜歡的


這樣就是黑的

但因為每個

LATEX都要打這麼多字才能包起來很麻煩
所以可以用
LATEX
中的
define
功能

$\def\bk{\color{#333333}}$

可以在內容的第一行打上這個
在這以下的東西都可以用\bk來代替\color{#333333}
當然你也可以把bk改成其他名稱

雖然這樣會留下一個.的痕跡,但只要在同一行打字就幾乎可以蓋掉

但是又有一個問題,就是右邊那一個豎槓,很醜
所以有一個極為神奇的方法可以把它弄掉
這種方法又麻煩又有很大的副作用
使用前要三思



沒錯,要改
html

反正就是要把$包起來的地方
<font color=white>...</font>包起來

變這樣,儲存之後你會發現字都變白的
對,這就是他很大的副作用


那條線就消失了,漂亮多了

然後如果真的要這樣手動改的話真的會改到死
所以我就直接丟程式碼:

#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+zDOS執行終止
  3. 最後把輸出的
    new source code
    複製丟回去
    source code
    的地方

然後如果想把變白色的的東西變回來

全選後點這邊就可以了

所以真的先在

hackmd上寫好再弄上去是會好的方法

Special Judge
寫法

就是要自己寫評斷方式
上面有寫一個一個說明,但是範例是

python的所以應該有很多人就放棄了
這裡先丟一個c++範例

#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

補充:

Special Judge在正常情況下是看不到上傳者的程式碼本身的,
所有想要寫那種要看別人程式碼裡面東西的題目的可以死心了

然後

Special Judge中用不到的檔案可以空著沒關係

其他

關於某些東西的模板
可以看這裡:https://hackmd.io/@chrislaiisme/templates
(新版:https://chrislaiisme.github.io/templates/)(點進去先重新整理畫面)
只是我生測資的模板應該滿難懂的,但好用

剩下就加油了,相信各位都可以出出優質的好題
(P.S. 現在是凌晨3點,我在吃美味蟹堡)