新生初賽題目、解題思路、參考代碼一覽
A. 拒絕虐狗
Problem Description
CZJ 去排隊打飯的時候看到前面有幾對情侶秀恩愛,作為單身狗的 CZJ 表示很難受。
現在給出一個字符串代表 CZJ 前面的隊列。你可以幫他一把把那些情侶分開嗎?
Input
第一行是一個\(T\),代表數據的組數\(T\)(\(T\leq 100\))。
每組一個只包含大寫字母的字符串\(p\)(\(1\leq len_{p}\leq 1000\)),表示 CZJ 前面的隊列,保證不會有連續三個及以上相同的字母序列
Output
將隊列中相同且相相鄰的字母用“1”隔開並輸出。
Sample Input
2
AABB
ABBA
Sample Output
A1AB1B
AB1BA
解題思路
- 簽到題,由於保證不會出現3個及以上的連續相同字符,所以每次判斷一下上一個字符就好;
- 直接在線處理,時間復雜度\(O(len_{p})\)。
參考代碼
#include <stdio.h>
int main() {
int T;
// 讀入數據組數,並忽略本行回車
scanf("%d%*c", &T);
while (T--) {
// 每次先初始化上一次讀入的字符為EOF
int readChar, lastChar = EOF;
// 不斷讀入一個字符,直到回車
while (readChar = getchar(), readChar != '\n') {
// 如果讀入的字符和上次讀入字符相同,則先輸出'1'分開
if (readChar == lastChar) putchar('1');
// 經過前面的處理后,把本次讀入的字符輸出
putchar(readChar);
// 重置上次讀入的字符
lastChar = readChar;
}
// 單個數據結束,輸出換行
putchar('\n');
}
return 0;
}
比賽提交代碼中發現的問題
- 有選手直接提交了exe、txt文件而不是源碼文件,導致編譯錯誤;
- 部分選手對於循環理解有偏差,
for(i=0; i<=n; i++)
是執行n+1遍的,而數據只有n組,導致一直卡輸入直至超時; - 部分選手誤認為要讀入全部數據后才能輸出;實際上可以計算完一個情形后再讀入下一個情形計算;
- 接上一點,其中又有部分選手因此開了像
char a[105][1600];
的數組。在內存限制范圍內開數組沒問題,但是不能在main()等函數中開這么大的數組;詳見C/C++堆棧空間分配; - 接上一點,其中又有部分選手開了大小剛好為1000的數組;實際上是對字符串的存儲理解有偏差,字符串需要額外存儲一個結束符'\0',因此導致越界訪問內存;
- 有選手使用了C++的string類,但可惜基本功不扎實,存在錯誤使用;
- 有選手一開始提交的代碼中出現了“Please input ...”這樣的輸出,導致WA;
- 有選手把==寫成了=(比如
if(a==1)
成了if(a=1)
),導致循環不結束直至TLE。
### B. Zyj 消極比賽
Problem Description
ACM 比賽剩下最后 10 分鍾,其他人都已經收拾好東西准備走人,Zyj 才從睡夢中醒來。
Zyj 可以看到所有其他人的過題情況,他希望得到的名次在 a 到 b 之間,問有幾種可選擇的方案?
(假設其他人的提交時間即使加上罰時也早於 Zyj 的提交,畢竟剩下 10 分鍾了都)
Input
第一行為 T (0 < T < 100),代表有 T 組數據。
每組數據中第一行為兩個數字 m n a b,由空格隔開,代表這次比賽有 m 道題 (由於字母數量的限制,0 < m < 27)、
n 個其他人、Zyj 希望得到的名次 x 滿足 a < x < b ( −1 < n, a, b < 1000)。
下一行為 Zyj 解決每道題需要的秒數 \(0\leq t_i < 999\)。
下面 n 行為每個人每道題的通過情況,1 為已通過,0 為未通過。
Output
對每個樣例,輸出 Case #k: ans,其中 k 為樣例編號,ans 為方案數量。
Sample Input
1
4 1 0 2
300 300 300 300
0 0 0 1
Sample Output
Case #1: 6
Hint
樣例解釋:
Zyj 必須做出至少 2 題,又因為每個題的解題時間為 300 秒即 5 分鍾,Zyj 也只能做最多 2 題,
所以 Zyj 做兩題有 \(C_4^2 = 6\) 種方案。
解題思路
- 初賽這題是求組合數。但是你有沒有想過為什么能用求組合數的方法來做呢?
- 還記得嗎?初賽這道題沒有對Zyj做每題作出限制。也正因為如此,Zyj無論做哪一題都是等價的,所以對於無序集合,我們可以求組合數。
- 但是這里更接近實際情況,Zyj做每一題的時間都是不一樣的。有可能這道題做了,剩下的題就沒時間做了。由於剩下的時間有限,這是個求滿足特定條件的子集的問題。
- 能不能用枚舉的方法呢?
- 選一道題,根據剩下的時間再選一道題,重復下去,總能把所有情況列舉出來。
- 但是你想啊,一個大小為\(m\)的集合,它的非空子集有 $2^m-1 $個;
- 一個不小心你可能需要 \(O(2^m)\) 到甚至 \(O(m!)\) (姿勢不對時)的復雜度把所有可行子集都枚舉出來。
- 枚舉太暴力了,我們可以記憶化搜索。
- 我們定義一個數組 dp[i][j][t] (\(j\leq i\))來表示在總共有 \(i\) 道題,Zyj睡醒后剩下 \(t\) 時間的情況下,Zyj做 \(j\) 題的方案數。
- 顯然,對於任意 \(1\leq i\leq 26\), \(0\leq t\leq 600\) (10分鍾=600秒), dp[i][0][t]=1 恆成立,均可算作1種情況。
- 設 c[i] 代表Zyj做第 \(i\) 題所需的時間,顯然, dp[i][i][t] = dp[i-1][i-1][t-c[i]];
- 而Zyj做 \(0<j<i\) 題時(dp[i][j][t]),不做第 \(i\) 題的方案數為 dp[i-1][j][t],做第 \(i\) 題並在前面的題選做 \(j-1\) 題的方案數為 dp[i-1][j-1][t-c[i]]。
- 綜上,我們可以得出 dp[i][j][t] = { dp[i-1][j][t] + dp[i-1][j-1][t-c[i]], if \(j\leq i\) }, { 0, if j > i }, { 1, if j = 0 }。
- 時間復雜度 \(O(300m^2)\);空間復雜度 \(O(600m^2)\),用滾動數組優化到 \(O(600m)=15600\)。
參考代碼
#include <stdio.h>
#include <string.h>
const int MaxTime = 600;
int m, n, a, b;
int dp[27][601];// dp[j][t]代表Zyj在t時間內做j題
int solveCount[27];// solveCount[i] 記錄其他人做i題的人數
void init() {
memset(dp, 0x0, sizeof(dp));
for (int iterTime = 0; iterTime <= MaxTime; ++iterTime)
dp[0][iterTime] = 1;
memset(solveCount, 0x0, sizeof(solveCount));
}
void prepare() {
int costTime;// 讀入Zyj做每一題所需的時間
for (int iterProb = 1; iterProb <= m; ++iterProb) {
scanf("%d", &costTime);
// j=0 的情況不需要處理,已經初始化好了
// 由於使用了滾動數組,從大到小更新
for (int iterSolv = iterProb; iterSolv > 0; --iterSolv)
// 直接從Zyj解該題所需時間考慮,剩下時間小於該時間的話Zyj也解不出來
for (int iterTime = costTime; iterTime <= MaxTime; ++iterTime)
dp[iterSolv][iterTime] += dp[iterSolv - 1][iterTime - costTime];
}
}
void readSolves() {
// 讀入n個人的做題情況
for (int iterPerson = 0; iterPerson < n; ++iterPerson) {
int acc = 0, solvedTag;// acc用於累加每個人做題總數,solvedTag表示是否解題
for (int iterProb = 0; iterProb < m; ++iterProb) {
scanf("%d", &solvedTag);
acc += solvedTag;
}
// 累加總共做出acc道題的人數
++solveCount[acc];
}
}
void read() {
scanf("%d%d%d%d", &m, &n, &a, &b);
prepare();
readSolves();
}
int work() {
// rank代表Zyj做i題以上能拿的名次
// Zyj有拿第一的可能,但也一定是同題數的最后一名
int rank = 1, result = 0;
// 從全做m題開始遍歷到只做1題
for (int iterSolv = m; iterSolv > 0; --iterSolv) {
rank += solveCount[iterSolv];
if (a < rank && rank < b)
result += dp[iterSolv][MaxTime];
}
// 若做0題,則與其他做0題的人並列排名
if (a < rank && rank < b)
result += dp[0][MaxTime];
return result;
}
int main() {
int T;
scanf("%d", &T);
for (int cse = 1; cse <= T; cse++) {
init();
read();
printf("Case #%d: %d\n", cse, work());
}
return 0;
}
### C. 星界游神 llm
Problem Description
L2m 向星界游神學藝,星界游神教會他一種技能后大笑“后繼有人”后溘然長逝。於是 L2m 成了新的星界游神。
L2m 比較強,可以收集紅藍綠三種調和之音。但是原本的星界游神比較弱,教給 L2m 的技能要消耗同等數量的調和之音才能發出。
Zyj 就想知道,L2m 最多能發多少次技能,好在 L2m 技能用光后打敗他。
Input
第一行只有一個正整數 T (\(T\leq 1000\)),代表了 T 個情形。
接下來的 T 行,分別是三個整數 \(0<R,B,G<10^9\),代表大佬 L2m 所擁有的紅、藍、綠三種調和之音。
Output
請你對於每種情況輸出大佬能發出多少次技能。
Sample Input
1
1 3 2
Sample Output
1
解題思路
- 這是2015年廣東省賽原題,我改了下題面作為簽到題放上來啦。
- 因為需要消耗同等數量的調和之音,所以三個變量之間存在制約,釋放技能次數取決於三者的最小值。
- 求最小值應該很好求吧,1. if-else; 2. 條件表達式
(a<b)?(a<c?a:c):(b<c?b:c)
; 3. 使用min函數std::min(a,b)
; 4. 自己寫個min函數int min(int a,int b){return a<b?a:b;}
- 時間復雜度:O(1)
#include <stdio.h>
#include <algorithm>
using std::min;
int main() {
int T, R, B, G;
scanf("%d", &T);
while (~scanf("%d%d%d", &R, &B, &G)) {
printf("%d\n", min(min(R, B), G));
}
return 0;
}
比賽提交代碼中發現的問題
- 交錯代碼了..把別的題目的代碼交上來了..;
- 又提交了exe、txt上來..;
- 惡意提交exe,當我想打開源碼看交了什么鬼時,程序卡了好久..;
- 提交的代碼中含有
system("pause");
,導致編譯錯誤or超時(大概是用VC6.0寫的?我建議選手們除了寫作業和考試外不要使用VC6.0); - 對R,G,B三個數字做奇怪的加權or除法,沒看懂為什么要這么做;
- 想將計算結果保存后再一次性輸出..結果保存的還不對;還有輸出漏了一個
for(i=1;i<T;i++)
;呃..再強調一遍,我們是可以計算完一個情形后立刻輸出的; - 輸出的時候忘記換行。你想啊,比如你要分別輸出1和2,結果你輸出了12,能一樣嗎;
- 有選手把所有情形的答案全部加起來了才輸出,啊,咋回事啊,是我的題面閱讀性差嘛QAQ
### D. Oyk 剪紙
Problem Description
Oyk 又和 Zlm 剪紙了。因為以往的剪紙都是一個方向的,他們決定換個方式。
把一張矩形紙分割為 a*b 的網格,每次可以剪去 n 個格子以內組成的矩形。
Oyk 希望能剪到最后一個格子,那么他應該先剪還是后剪?
如 n=5 時,下列方案中 1,2,5 為可行方案,3 不是(因為它不是個矩形),4 也不是(因為由 6>5 個格子組成)
注意,剪完之后如果紙斷開了,可以任選一部分剪,如同它們沒有斷開一樣,只是不可能同時剪到其中的多個部分;剪出來的部分就扔了,不再動。
Input
輸入有多組(少於1000組)數據,處理到文件尾。每行有三個數字,a,b和 n(均為小於10000的正整數),由空格隔開。
Output
對每個樣例。輸出 1 如果應該先走,否則輸出 2。
Sample Input
1 3 1
1 3 3
Sample Output
1
1
Hint
1 3 1 中,不管剪哪一個格子,總會剩下兩個,Zlm 再剪一個剩下一個,Oyk 能剪到最后一個。
1 3 3 中,Oyk 可以一次把整個紙剪了,Oyk 還是能剪到最后一個。
解題思路
- 找規律題,但我不會。於是又讓Oyk教了我一次剪紙....
- Oyk:推薦先思考的經典問題:
- 一個圓桌上放硬幣,先放滿(剩余空間不能再容納)為勝,先手怎么放才能獲勝;
- 變種:一個直徑d圓桌放直徑p圓盤,先放滿為負,問先手能不能贏;
- 一維剪紙:一條長紙帶划為n格;一條環形紙帶划為n格;
- 首先觀察任意大小的矩形網格紙,知道無論先手剪去哪部分,后手總能根據對稱性剪去一樣的部分;如果先手能夠先挖掉矩形紙的中間一塊,而使得剩下的紙仍然具有中心對稱的性質,則獲得了后手必勝局勢(注意不連續的紙不能同時剪,並必須剪小矩形)。
- 由於對稱性的存在,我們構造最小不同例子,分別是邊長為\(1\times 1\)、\(1\times 2\)、\(2\times 2\)的矩形紙:
- 先驗證它們是最小不同例:
- 對於\(1\times 1\)的情況,先手總是勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
- 對於\(1\times 2\)的情況,先手只能全部拿完才獲勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
- 對於\(2\times 2\)的情況,先手只能全部拿完才獲勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
- 不失一般性,考察\(2\times 1\)的情況,發現旋轉后與\(1\times 2\)的情況等價,不需另外考慮。
- 對於三個不同情況的等價類,邊長的奇偶性相同,因此得出三種先手必勝策略:同為奇取1,同為偶取4,一奇一偶取2;
- 顯然,如果需要取的最少格子數不大於N的話,先手無法占先取得對稱局勢,而后手總能在第一局補齊獲取對稱局勢所需的格子,從而獲得勝利。
- 先驗證它們是最小不同例:
- 總結:根據兩邊長奇偶性考察N的大小。
- 時間復雜度:O(1)
參考代碼
#include <stdio.h>
int main() {
int a, b, n;
while (~scanf("%d%d%d", &a, &b, &n))
puts(4 >> (a % 2 + b % 2) > n ? "2" : "1");
return 0;
}
### E. Zyj 穿越到古代
Problem Description
Zyj 又消極比賽了,這次他被 SCNU 的大佬們追殺穿越到了古代。他成了古希臘的一個將軍,但是他的軍隊被 Hyr 帶領 SCNU 的大佬們圍困在山頂。眼看突破無望,但是 Zyj 軍隊里的將士又不肯投降,就想以死殉職。但是 Zyj 就不願意了。他想:寶寶心里苦啊,不就是比賽的時候睡個覺嘛。於是他想出一個方法讓自己逃脫制裁。
將軍隊里面剩下的所有人從 1 到 N 開始編號,每次數 M 個人,從 1 開始數。被數到的那個人以死殉職。然后下一個人繼續從 1 開始數。Zyj 要逃脫就只能成為最后一個殉職的人,這樣他就能乖乖投降了。請你告訴 Zyj 要選編號是多少才能逃脫制裁。
比如有 3 個人,編號 1,2,3。每次數 2 個,第一個殉職:2。剩下 1,3。第 2 個殉職:1。只剩下3。於是 zyj 一開始選擇編號 3 可以逃脫。
Input
有多組數據,數據不超過 100 組,輸入到文件尾結束。每組數據共一行兩個數,第一個數 N 代表人的總數,第二個數 M 代表每次數多少個人(\(0\leq N\leq 1000\),\(0\leq M\leq 1000000000\))。
Output
輸出一行,一個整數代表 zyj 一開始要選哪個編號才能活着。
Sample Input
5 3
4 6
Sample Output
4
3
解題思路
- 不是我出的題,只是我造的數據。數據如下:
500 999999999
500 999999797
500 999999792
500 643242600
500 123456789
491 999999999
499 999999797
503 999999792
509 643242600
521 123456789
500 1
523 1
- 可能大家都在課本上學過循環數數的方法,但這是很慢的,復雜度高達\(O(NM)\),妥妥的TLE。
- 當然,如果你不死心,一定要模擬循環數數,那也可以的,因為只有\(N\)個人,當\(M>N\)時,數到最后一個人只會再從第一個人開始數起,不可能數出第\(M\)個人來,所以循環數數的長度應該是\(M\%N\)。時間復雜度\(O(\sum^{N}_{i=2}M\%i)=o(N^2)\)。
- 我認為我的題是必須良心的,所以放過了\(O(N^2)\)的算法,因此用一個數組標記每個人,每次你算出那個人(數組元素的下標)之后,直接把數組后面的元素往前移就相當於刪掉那個人了,也就是重新標號。無論是int數組前移,還是結構體數組重標號,還是vector刪除元素,都是\(O(N^2)\)的時間復雜度。
- 可惜場上沒有多少同學想到要\(M\%N\),幾乎都直接循環\(M\)的長度。我真的不知道原因在哪里,是C語言課上的太水了嗎?請你們在自己的電腦上測試一下,你的電腦跑\(10^9\)長度的循環需要多長時間。
- 正解:
- 假設當前數號到\(num\),好的他已經出局了,原來的第\(num+1\)個人成了新的第\(num\)個人;
- 要從新的第\(num\)個人開始往后數出第\(M\)個人出局,而總人數只有\(N\)個人,所以下一次會數號到\(num'=(num+M)\%N\),第\(num'\)個人出局,類推。
- 因此得到一個遞推公式:\(R_i=(R_{i-1}+M)\%N\),當\(i=N\)時,總人數只剩下一個人,\(R_N\)就是Zyj想要的編號。
- 時間復雜度:\(O(N)\)
參考代碼
#include <stdio.h>
int main() {
int n, m, i, s = 0;
while (~scanf("%d%d", &n, &m)) {
s = 0;
for (i = 2; i <= n; i++) {
s = (s + m) % i;
}
printf("%d\n", s + 1);
}
return 0;
}
比賽提交代碼中發現的問題
- 跑了長度為\(M\)的循環,不再多說;
- 以為數據還會輸入一個整數\(T\),但是這里是要讀到EOF的;
- 有同學用了鏈表..但是鏈表不能做到隨機查詢的話,還是模擬循環數數而已;
### F. 貪吃蛇
Problem Description
貪吃蛇是一個很有名的游戲。小蛇每吃掉一個食物,就會長長一格;撞到障礙物或自己,就會結束游戲。
這里把貪吃蛇的規則略作修改:
- 蛇所在世界的長為 W,高為 H,坐標為左上角 (1,1) 至右下角 (W,H)。
- 剛開始時,蛇長度為 1,位於 (1,1) 處,初始方向向東。
- 每秒可以按下 W 表示向上,A 表示向左,S 表示向下,D 表示向右,或不按按鈕,來試圖對蛇進行轉向,但只有試圖的轉向與當前方向垂直時,轉向有效。
- 每秒在嘗試轉向后,向新方向移動 1 格(如果轉向成功)。如果吃到食物,長度增加 1,否則長度不變。
- 世界是環形的,即從右邊離開邊界會從左邊重新進入,以此類推
- 撞到自己,蛇長度恢復為 1,方向、位置不變。
Input
有多組樣例,處理到文件結束。每組樣例第一行為三個數字 W,H,N(0<W,H<500,0<N<100000)
下面 N 行,每行:第一個字符為 WASDNwasdn 之一,字母表示那一秒的按鍵,N 表示那一秒不按鍵;大寫表示吃到食物,小寫表示沒有。后面緊接一個空格,然后一個 64 位無符號整數\(a_i\)。
Output
對每個樣例,輸出一行,其值為 \(\sum_{t=1}^{N}a_t \sum_{x=1}^{W}\sum_{y=1}^{H}3^{yW+x}c_{txy} mod 2^{64}\),其中 \(c_{txy}\) 表示第 t 秒 (x,y) 處,有蛇為 1,沒有蛇為 0
Sample Input
3 2 8
a 0
s 0
D 0
N 0
n 0
w 1
s 0
n 1
Sample Output
15552
樣例解釋:
輸出為 \(1(3^{1\times 3 + 2} + 3^{2\times 3 + 1} + 3^{2\times 3 + 2}) + 1\times 3^{1\times 3 + 2} mod 2^{64} = 9234\)
解題思路
- 模擬題。直接模擬就好,沒什么特別的技巧。
- 時間復雜度:O(玄學)
參考代碼(出題人的代碼)
#include <stdio.h>
#include <stdlib.h>
#include <queue>
int z[500][500], q;
unsigned long long vu[300000]={1};
inline void setp(int x, int y) {
z[x][y]=q;
}
inline void clrp(int x, int y) {
z[x][y]=0;
}
inline bool seep(int x, int y) {
return z[x][y]==q;
}
int main() {
int w,h,n;
for (n=1; n<300000; n++) {
vu[n] = vu[n-1] * 3;
}
while (~scanf("%d%d%d",&w,&h,&n)) {
char c[2];
unsigned long long val=0, ali, map=vu[w+1];
q++;
int x=1, y=1, d='d';
std::queue<int> L;
L.push(w+1);
while (n--) {
scanf("%s%llu", &c, &ali);
c[1]=c[0]<'a';
if(c[1]) c[0]+=32;
switch (c[0]) {
case 'd': case 'a':
if(d=='w' || d=='s') d=c[0]; break;
case 'w': case 's':
if(d=='a' || d=='d') d=c[0]; break;
}
switch (d) {
case 'a':
x--;
if(x==0) x=w;
break;
case 's':
y++;
if(y>h) y=1;
break;
case 'd':
x++;
if(x>w) x=1;
break;
case 'w':
y--;
if(y==0) y=h;
break;
}
if (c[1]) {
//got new
} else {
int t=L.front()-1;
L.pop();
int y=t/w, x=t%w+1;
clrp(x,y);
map-=vu[w*y+x];
}
if (seep(x,y)) {
map=0;
q++;
std::queue<int>t;
std::swap(L,t);
}
L.push(y*w+x);
setp(x,y);
map+=vu[w*y+x];
val+=ali*map;
}
printf ("%llu\n", val);
}
}
### G. equation
Problem Description
給出一個各項系數均非負的多項式\(f(x)\),是否存在\(x>0\)使得\(f(x)=1\)?
Input
每行一個樣例,有多個數字(均小於25000,最多精確到6位小數)分別表示各項系數。具體地說,第\(i\)個數字表示\(f\)的\((i-1)\)次方項系數。沒有列出的數字系數為0。
Output
若有解則輸出解,若有多個解則從小到大輸出,精確到四位小數,若無解輸出空行。
Sample Input
0.5 1
Sample Output
0.5000
Hint
該輸入表示\(f(x)=1x+0.5=1\)解為\(x=0.5\)
解題思路
- 二分法。
- 當各項系數非負,且\(x\geq 0\)時,\(f(x)\geq 0\)恆成立且\(f(x)\)在\([0, +\infty)\)上單調遞增。
- 如果\(\exists x>0, f(x)-1=0\),則\(f(0)-1<0\),即\(f(0)<1\);
- 考察增長最慢的一個有解函數\(f(x)=1\times 10^{-6}x=0.000001x\)(所給數據只精確到6位小數),也存在\(f(10^6)=1\),取最近的素數\(f(10^6+3)>1\);
- 綜上,我們可以在\((0, 10^6+3)\)區間上二分出滿足\(f(x)=1\)的答案。二分精度為\(10^{-6}\)。
- 當常數項大於或等於1,則\(\forall C\geq 1, \forall x>0, f(x)=g(x)+C>1\)恆成立,此時無解。
- 當\(f(10^6+3)<1\),說明出現比\(1\times 10^{-6}\)更小的系數,即系數全為0,此時無解。
- 時間復雜度:設\(f(x)\)最多有N項,最壞\(O(N log_2((10^6+3)\times 1/10^{-6}))=O(40N)\)
參考代碼(出題人的代碼)
#include <stdio.h>
#include <stdlib.h>
double g[1000007];
int n=0;
double f(double x) {
double ans=0, leg=1;
for (int i=0; i<n; i++) {
ans+=g[i]*leg;
leg*=x;
}
return ans;
}
int main() {
char c;
while (~scanf("%lf%c",g+n++,&c)) {
if(c=='\n') {
if (g[0]>=1 || f(1000009)<1) puts("");
else {
double L=0, R=1000009;
for (; R-L>1e-6; ) {
double M=(L+R)*.5;
if (f(M)<1) L=M;
else R=M;
}
printf("%.4f\n", L);
}
n=0;
}
}
}
01/22/17更新:牛頓迭代法
由於逆天出題人二分時沒有考慮精度誤差,導致部分數據與實際事實不符。這里給出牛頓迭代解
注:
- 已與Mathematica對比正確;
- deriv()是近似求導;
- 實際上我更傾向於迭代若干次,比如100次,而不是迭代到指定精度。
#include <stdio.h>
#include <math.h>
#include <utility>
const int MAXM = 1000003;
const double Eps = 1e-7;
int count = 0;
double coeff[1000007];
std::pair<double, double> calc(double x) {
double func = .0, deriv = .0, p = 1.0;
for (int i = 0; i < count; ++i) {
double item = coeff[i] * p;
func += item;
deriv += i * item;
p *= x;
}
return std::make_pair(func, deriv);
}
double func(double x) {
double res = .0, p = 1.0;
for (int i = 0; i < count; ++i) {
res += coeff[i] * p;
p *= x;
}
return res;
}
double deriv(double x) {
return (func(x + Eps / 2.) - func(x - Eps / 2.)) / Eps;
}
inline int test() {
return coeff[0] < 1 && func(MAXM) > 1;
}
void work2() {
if (test()) {
double x0 = .0, x = 1.0;
while (fabs(x - x0) > Eps) {
x0 = x;
//x = x - (func(x) - 1) / deriv(x);
std::pair<double, double> newton = calc(x);
x = x - (newton.first - 1) / newton.second;
}
if (x > 0) printf("%.4f", x);
}
putchar('\n');
}
int main() {
/* freopen("new_in.txt", "r+", stdin);
freopen("new_out2.txt", "w+", stdout);*/
char c;
while (~scanf("%lf%c", &coeff[count++], &c)) {
if (c != '\n') continue;
work2();
count = 0;
}
return 0;
}
### H. 續時間
Problem Description
Czj快畢業了,但他還想留在學校里,於是找到了SCNU最強大的魔法師Wwj買時間。
Wwj覺得他太蠢了,想要為難他一下,就用了分身術boom的一下變成了好多個Wwj。
Ccr經過一番偵查,發現\(N\)個Wwj的法力不一,第\(i\)個Wwj只能給Czj延長\(T_i\)的在校時間,但收費取決於心情,是心情的\(P_i\)倍,而當前Wwj的心情是\(D_i\)。
Hyr經過一番偵查,發現魔法值不一定要完全用完,比如當前Wwj收費\(C\),Czj可以拿出\(C * a\%\)的財寶,來換取對應只有\(T_i * a\%\)的時間。
即使Czj家財萬貫,天天請Lht奶茶喝,也經不住如此高昂的花費。所以他只帶總共價值為M的財寶,去找Wwj的魔法分身換時間。
Lzp想知道Czj最多能延遲多長的在校時間。
Input
第一行為一個正整數\(T<50\),代表有\(T\)種情況。
第二行輸入每種情況的\(N\)和\(M\),均是正整數,\(N\leq 1000\),\(M\leq 10000\)。
接下來\(N\)行,每行有三個正整數,分別是\(T_i\)、\(P_i\)、\(D_i\),均不大於\(100\)。
Output
對於每種情況,輸出Czj最大能延長的時間,由題意知可能存在小數,請保留4位小數。
Sample Input
1
3 10
10 3 2
5 2 2
5 3 2
Sample Output
15
解題思路
- 我出的題都是良心題。這題是用貪心法做。我知道新生想在短時間內想出來還是挺難的,定位是中等題(什么叫難題?你看上面那兩個..正常人會做的?)
- 本來我的題面是有強調 a% 的,但是我寫LaTeX公式時沒有對百分號 % 做轉義處理,導致pdf上沒顯示出來,影響部分同學沒看懂題,十分抱歉。
- 正解:
- 因為財富是一定的,所以隱含了要花最少的代價,買到最多的商品。想通了這點,你就知道應該要去計算性價比(或單價)(單價與性價比互為倒數);
- 但這里的價格計算方式不一樣,因為最終的花費是\(P_i\times D_i\),所以性價比是\(\frac{T_i}{P_i\times D_i}\);
- 對所有種類商品的性價比進行排序,優先買性價比最高(單價最低)的。最后由於可以買部分數量商品而不需要全買(即允許購買 a%),把剩余的錢數跟性價比一乘,就是還能買的 a% 數量的商品。
- 貪心策略證明:
- 如果在一個可行的購買方案中,存在性價比更高的商品有剩余(沒購買),那么購買該性價比更高的商品,而不夠買原方案中相應總價的性價比最低商品,總能獲得更多數量的商品。
- 因此所有購買性價比最高商品的局部最優解的總和是全局最優解。
參考代碼(這里采用結構體排序,並用了C++語法)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
void generate() {
FILE *outFile = freopen("in.txt", "w+", stdout);
int T = rand() % 10 + 11;
fprintf(outFile, "%d\n", T);
while (T--) {
int N = rand() % 1000 + 1;
int M = rand() % 10000 + 1;
fprintf(outFile, "%d %d\n", N, M);
while (N--) {
int Ti = rand() % 100 + 1;
int Pi = rand() % 100 + 1;
int Di = rand() % 100 + 1;
fprintf(outFile, "%d %d %d\n", Ti, Pi, Di);
}
}
}
struct Magic {
int T, P, D, cost;
double ratio;
bool operator<(const Magic &c) const {
return fabs(ratio - c.ratio) > 1e-7 && ratio < c.ratio;
}
} magic[1010];
int main() {/*
generate();
freopen("in.txt", "r+", stdin);
freopen("out.txt", "w+", stdout);*/
int T, N, M;
scanf("%d", &T);
while (T--) {
scanf("%d%d\n", &N, &M);
for (size_t i = 0; i < N; ++i) {
int Ti, Pi, Di;
scanf("%d%d%d\n", &magic[i].T, &magic[i].P, &magic[i].D);
magic[i].cost = magic[i].P * magic[i].D;
magic[i].ratio = (double) magic[i].T / magic[i].cost;
}
std::sort(magic, magic + N);
double res = 0.;
for (size_t i = N - 1; i >= 0; --i)
if (M > magic[i].cost) {
M -= magic[i].cost;
res += magic[i].T;
} else {
res += M * magic[i].ratio;
M = 0;
break;
}
printf("%.4f\n", res);
}
return 0;
}
出題及解題總結
我校ACM吃棗葯丸
建議、意見、吐槽
歡迎在下方評論區提出問題,12月內我都會回復and更新。
本文基於
知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議發布,歡迎引用、轉載或演繹,但是必須保留本文的署名BlackStorm以及本文鏈接http://www.cnblogs.com/BlackStorm/p/SCNUCPC_2016_For_Freshman_Final_Solution.html,且未經許可不能用於商業目的。如有疑問或授權協商請與我聯系。