SCNU ACM 2016新生賽決賽 解題報告


新生初賽題目、解題思路、參考代碼一覽

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;
}

比賽提交代碼中發現的問題

  1. 有選手直接提交了exe、txt文件而不是源碼文件,導致編譯錯誤;
  2. 部分選手對於循環理解有偏差,for(i=0; i<=n; i++)是執行n+1遍的,而數據只有n組,導致一直卡輸入直至超時;
  3. 部分選手誤認為要讀入全部數據后才能輸出;實際上可以計算完一個情形后再讀入下一個情形計算;
  4. 接上一點,其中又有部分選手因此開了像char a[105][1600];的數組。在內存限制范圍內開數組沒問題,但是不能在main()等函數中開這么大的數組;詳見C/C++堆棧空間分配;
  5. 接上一點,其中又有部分選手開了大小剛好為1000的數組;實際上是對字符串的存儲理解有偏差,字符串需要額外存儲一個結束符'\0',因此導致越界訪問內存;
  6. 有選手使用了C++的string類,但可惜基本功不扎實,存在錯誤使用;
  7. 有選手一開始提交的代碼中出現了“Please input ...”這樣的輸出,導致WA;
  8. 有選手把==寫成了=(比如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;
}

比賽提交代碼中發現的問題

  1. 交錯代碼了..把別的題目的代碼交上來了..;
  2. 又提交了exe、txt上來..;
  3. 惡意提交exe,當我想打開源碼看交了什么鬼時,程序卡了好久..;
  4. 提交的代碼中含有system("pause");,導致編譯錯誤or超時(大概是用VC6.0寫的?我建議選手們除了寫作業和考試外不要使用VC6.0);
  5. 對R,G,B三個數字做奇怪的加權or除法,沒看懂為什么要這么做;
  6. 想將計算結果保存后再一次性輸出..結果保存的還不對;還有輸出漏了一個for(i=1;i<T;i++);呃..再強調一遍,我們是可以計算完一個情形后立刻輸出的;
  7. 輸出的時候忘記換行。你想啊,比如你要分別輸出1和2,結果你輸出了12,能一樣嗎;
  8. 有選手把所有情形的答案全部加起來了才輸出,啊,咋回事啊,是我的題面閱讀性差嘛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. 對於\(1\times 1\)的情況,先手總是勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
      2. 對於\(1\times 2\)的情況,先手只能全部拿完才獲勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
      3. 對於\(2\times 2\)的情況,先手只能全部拿完才獲勝。在邊的兩側同時添加一行或一列不影響對稱性,在邊的單側添加會由於對稱性而轉變為另兩種情況;
      4. 不失一般性,考察\(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;
}

比賽提交代碼中發現的問題

  1. 跑了長度為\(M\)的循環,不再多說;
  2. 以為數據還會輸入一個整數\(T\),但是這里是要讀到EOF的;
  3. 有同學用了鏈表..但是鏈表不能做到隨機查詢的話,還是模擬循環數數而已;

### F. 貪吃蛇

Problem Description

貪吃蛇是一個很有名的游戲。小蛇每吃掉一個食物,就會長長一格;撞到障礙物或自己,就會結束游戲。
這里把貪吃蛇的規則略作修改:

  1. 蛇所在世界的長為 W,高為 H,坐標為左上角 (1,1) 至右下角 (W,H)。
  2. 剛開始時,蛇長度為 1,位於 (1,1) 處,初始方向向東。
  3. 每秒可以按下 W 表示向上,A 表示向左,S 表示向下,D 表示向右,或不按按鈕,來試圖對蛇進行轉向,但只有試圖的轉向與當前方向垂直時,轉向有效。
  4. 每秒在嘗試轉向后,向新方向移動 1 格(如果轉向成功)。如果吃到食物,長度增加 1,否則長度不變。
  5. 世界是環形的,即從右邊離開邊界會從左邊重新進入,以此類推
  6. 撞到自己,蛇長度恢復為 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更新:牛頓迭代法

由於逆天出題人二分時沒有考慮精度誤差,導致部分數據與實際事實不符。這里給出牛頓迭代解
注:

  1. 已與Mathematica對比正確;
  2. deriv()是近似求導;
  3. 實際上我更傾向於迭代若干次,比如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上沒顯示出來,影響部分同學沒看懂題,十分抱歉。
  • 正解:
    1. 因為財富是一定的,所以隱含了要花最少的代價,買到最多的商品。想通了這點,你就知道應該要去計算性價比(或單價)(單價與性價比互為倒數);
    2. 但這里的價格計算方式不一樣,因為最終的花費是\(P_i\times D_i\),所以性價比是\(\frac{T_i}{P_i\times D_i}\)
    3. 對所有種類商品的性價比進行排序,優先買性價比最高(單價最低)的。最后由於可以買部分數量商品而不需要全買(即允許購買 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,且未經許可不能用於商業目的。如有疑問或授權協商請與我聯系


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM