很多人考noip之類的比賽永遠會發生一些奇怪的問題
比如說下面這兩位(來自我的兩位學長)
sliver n:spli,考得怎樣啊?
spli:就那樣啦,day1T1沒推出來規律,別的還好
silver n:看來你省一高分預定啊
幾周后。。。
silver n:分出來了嗎?
spli:出來了。。。
silver n:怎么了?
spli:別提了,day2T2掛了,沒拿上省一
silver n:嗯???你寫的不是正解么?
spli:是啊,可是我邊界處理出鍋了。。
silver n:。。。
好吧,以上是一個本能進隊的大佬無奈退役的經歷
同時也告訴了我們檢查的重要性
廢話少說,進入正題
很多時候我們在考場上總是會手殘的犯一些錯誤(比如邊界什么,大小寫,變量名)
然后你眼殘也看不出來,怎么辦呢?
我們今天就要講這些的克星:對拍
對拍不能解決一切問題,但卻可以解決你解決不了的問題
工具:一台電腦(沒錯,只用一台電腦)
軟件:命令提示符(別告訴我你家電腦上沒有這個東西),記事本,c++編譯器
下面我們來開始愉快的對拍之旅(這里講的是關於命令提示符的對拍,大家可以把他搬到c++里(用windows庫),但容易出鍋(我就出過幾次))
(注意,本篇文章默認使用windows系統!!!)
首先,我們先掌握幾個前置技能:
1.編寫你的認為是正解的程序和暴力程序(你要不會我也沒辦法了)
2.數據生成器(也就是隨機數)
先說隨機數
相信大家一般用隨機數都是用
#include<cstdlib>
庫中的rand()函數,但這顯然是不正確的
因為rand()是偽隨機數!!!
只要出題人想卡你,跑兩遍隨機數,打個表,數據避開一下
你的答案一不小心就出鍋了
所以,今天我們學如何生成真正的隨機數:
首先,我們先明確一點,c++里面的隨機數是一種算法
這個算法依賴於一個被稱為種子的數據
種子一般情況下是1
由於算法是固定的,所以種子不同,隨機數也就不同
所以關鍵就在於隨機數種子的生成。
我們一般使用時間種子生成器(time()函數)
(如果以時間為種子,這個種子一秒鍾一變,出題人想卡你的話那他簡直是瘋了)
怎么用呢?看下面:
首先我們要添加time()的頭文件:
#include<ctime>
之后,我們就可以使用這個函數了!!看圖
有了種子,我們就要利用種子生成隨機數了。
我們有請srand()函數登場。
srand()函數類似於cmp或者重載運算符函數之類的函數
在這里他被用於改變種子
用法:
先加頭文件(和rand()一樣):
#include<cstdlib>
然后看下面:
其實還有一點,是文件操作,我就默認你們會了(每次考試都會用)
好了,該有的都有了,下面我們就來一個制造數據的實例吧!
我以洛谷P3372【模板】線段樹 1為例:
題面:
題目描述
如題,已知一個數列,你需要進行下面兩種操作:
1.將某區間每一個數加上x
2.求出某區間每一個數的和
輸入輸出格式
輸入格式:
第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。
第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。
接下來M行每行包含3或4個整數,表示一個操作,具體如下:
操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k
操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和
輸出格式:
輸出包含若干行整數,即為所有操作2的結果。
數據規模:
對於30%的數據:N<=8,M<=10
對於70%的數據:N<=1000,M<=10000
對於100%的數據:N<=100000,M<=100000
(數據已經過加強^_^,保證在int64/long long數據范圍內)
由題目可知,我們要造的是n,m,n個原始節點,m次操作,且節點值的和在long long范圍內,n,m<=100000
具體實現請看代碼:
#include<iostream> #include<cstdio> #include<ctime> #include<cstdlib> #define rii register int i #define p 100000 using namespace std; long long seed; long long n,m; int main() { freopen("xds1.in","w",stdout);//文件操作,得到輸入文件 seed=time(0); srand(seed); n=rand();//windows下rand()max為32768,為了有一定的強度,我們乘一下 n*=n;//n,m這里你也可以手動取值 n%=p; m=rand(); m*=m; m%=p; // n=10,m=10; printf("%lld %lld\n",n,m); for(rii=1;i<=n;i++)//制造原始大小 { long long out=rand(); out*=2333; long long fh=rand();//添加負數 if(fh%2==0) { fh=1; } else { fh=-1; } printf("%lld ",out*fh); } printf("\n"); for(rii=1;i<=m;i++)//制造操作 { long long cz=rand(); if(cz%2==1)//生成修改操作的數據 { printf("1 ");//生成添加操作的數據 long long fh=rand();//添加負數 if(fh%2==0) { fh=1; } else { fh=-1; } long long l=rand(),r=rand(),val=rand(); l*=l; l%=n; if(l==0) { l=1; } r*=r; r%=n; if(r==0) { r=1; } val*=fh; val*=2333; if(l>r) { swap(l,r); } printf("%lld %lld %lld\n",l,r,val); } else //生成查詢操作的數據 { printf("2 "); long long l=rand(),r=rand(); l*=l; l%=n; if(l==0) { l=1; } r*=r; r%=n; if(r==0) { r=1; } if(l>r) { swap(l,r); } printf("%lld %lld\n",l,r); } } //區間和最大值上限在我寫的數據中為 //32768*100000*100000*2333=764477440000000000 // long long max=2^63-1=9223372036854775807,確保符合題意 }
下面是一組我造出來的數據(為了能放的下,n,m我手動設定為10)
10 10
1632165 90637 4875292 6168675 -6979981 3452128 5007170 -6946895 3268524 1131914
1 4 9 6775873
1 4 9 -1544790
1 6 9 6904023
2 5 6
1 1 4 3328405
1 1 6 2169929
2 4 9
1 1 9 3784852
1 1 6 -1164534
1 1 4 -5979013
怎么樣?還不錯吧?
3.windows文件操作(windows script語言)
這個大家可能不太熟悉,
不過沒關系,我會把可能用到的都講一遍。
(1)文件比較操作:
也就是比較兩個文件,判斷這兩個文件是否相等
我們一般用命令提示符(cmd.exe)操作(也可以寫成.bat批處理文件)
如果兩個文件相等,他會返回:(有點不智能,無法忽略空格和回車)
如果不相等,他會返回錯誤的地方:
很好用吧?
(2)啟動程序操作
RT,就是啟動一個位於與cmd/.bat同文件夾內的程序(其實也可以啟動不同文件夾里的,不過要寫路徑)
代碼極短:
就是這樣。
(3)暫停操作
你會發現:哎,我的代碼沒錯啊,為什么閃退了?
的確,你沒寫錯
但電腦默認執行完一個操作后自動跳出
所以看起來像閃退。
怎么辦呢?
我們可以讓電腦完成操作后“暫停”一下。
看代碼:
電腦會停下來,直到你按一下鍵盤
4.在c++語言中引入windws script語言:
這個很重要,因為循環什么的在windows script中很難實現
我們首先要在c++中引用頭文件:
#include<windows.h>
引用了這個頭文件,我們就可以使用system()函數
樣例:
system("pause");
在括號和引號中間寫你要執行的window語句即可
5.Sleep掛起操作
有的時候,你會發現對拍將兩個正確的輸出拍成錯的了,這是為什么呢?
很簡單,比如說上面那道線段樹,正解跑一秒,暴力4~5秒
但程序的命令行才不管你跑了幾秒,執行完一個立馬執行下一個
所以嘍,和可能你的數據還沒來得及輸出完,就已經被比較了
不錯才怪呢!
所以我們要寫Sleep()函數(注意S大寫!!)
暫停一段時間程序的運行,讓你的暴力好好跑一跑
怎么實現呢?很簡單,看下面:
頭文件:
#include<windows.h>
實現方法:
好啦,前置技能講完了,下一步我來講實例!
我們的實例還是上面的線段樹1。
首先,我們先將“正解”程序,暴力程序,數據生成器和對拍程序寫好,放在一個文件夾里
下面我給出對拍程序的源代碼(注意,數據生成器,“暴力”和“正解”務必提前編譯(也就是要exe)):
#include<iostream> #include<cstdio> #include<windows.h> using namespace std; int main() { int cnt=0; while(cnt<=10) { cnt++; system("start sjmaker.exe");//啟動數據生成器 Sleep(500);//等待數據輸出(極限數據輸出大約0.5s) system("start baoli.exe");//啟動“正解”和”暴力“ system("start zhengjie.exe"); Sleep(10000);//這個時間取決於你寫的暴力的最壞時間 if(system("fc baoli.out zhengjie.out"))//如果出鍋,就停下來 { system("pause"); break; } } }
OK, Debug完成!你可以去寫別的題了,只要時常回來看一看有沒有bug就行了。
如果你不想用c++的話......
當然,我們不會強制你使用c++引用系統命令,我們也可以直接用windows批處理文件來解決。
首先,先說明一下,.bat批處理文件就是windows命令,只不過放到了記事本里,又改了一下后綴名。。。
這里先補充一個操作:windows中的暫停操作(其實不是暫停,不過和暫停一個效果):
我們用這條語言來掛起程序一段時間
下面我們來看實例:
1.首先,我們新建一個文本文檔,在里面編寫系統語言:
(此處為源代碼,和c++中基本一致)
2.然后將文件后綴名改為.bat
運行后即可看到結果
你可能會問:這不是有bug么?只能運行一次???
的確,這點不太友好,因為windows命令提示符中不支持循環。。。
所以,你有兩條路可走,一條是在c++的循環中 system(“start duipai.bat”);
(與其這樣還不如在c++里直接寫呢。。)
還有一種是自調用bat(有些危險,手滑勿用)
我們在bat文件的最后一行寫下這樣一行代碼:
start duipai.bat
他就會在執行完上一個程序之后自動重復執行
(warning!!前面的暫停時間必須要開大,否則快速的重復執行會讓你死機!!(你的手都來不及關))。
番外篇:clock()函數
很有可能我們通過對拍,已經知道這個的答案是對的了,但是,我們有時候又擔心卡時間,怎么辦呢?
我們可以使用clock()函數
顧名思義,這是一個計時函數
他大體上是這樣的:
先定義變量:
clock_t start,stop;
在代碼的開始各跑一個clock()函數,比如說:
#include<time.h> #include<iostream> #include<cstdio> using namespace std; int main() { clock_t start,end; start=clock(); //這里寫你的源代碼 //。 //。 //。 //。 end=clock(); cout<<end-start; }
這樣就讀出了時間(單位:毫秒ms)
當然,這樣跑是會占用一定cpu時間的,如果發現略有超時(比如1000ms你跑了1050ms),別怕,正常情況還是能過的
當然,大家如果有更好的方法,歡迎私信聯系