日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人工智能 > ChatGpt >内容正文

ChatGpt

MAIGO的同济题解2

發布時間:2023/12/14 ChatGpt 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MAIGO的同济题解2 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Welcome to
Tongji Online Judge Solutions by Maigo Akisame Volume 2

我在TJU的所有AC程序及本題解均可在purety.jp/akisame/oi/TJU.rar下載。

purety.jp/akisame/oi/TJU.rar下載。
<script type="text/javascript"> for i=0 to 9 document.write "<th>題號<th>狀態" next num=1100 '1100 prob "ac",1 prob "ac",1 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 '1110 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "&nbsp",0 prob "ac",1 prob "ac",0 prob "ac",1 '1120 prob "&nbsp",0 prob "ac",1 prob "ac",1 prob "&nbsp",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 '1130 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",0 '1140 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",0 '1150 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",0 prob "ac",1 '1160 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",0 prob "ac",1 '1170 prob "ac",1 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",1 prob "ac",1 prob "ac",1 prob "&nbsp",0 prob "&nbsp",0 '1180 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",0 '1190 prob "ac",1 prob "ac",0 prob "&nbsp",0 prob "ac",0 prob "ac",0 prob "&nbsp",0 prob "ac",1 prob "ac",0 prob "ac",0 prob "ac",1 </script>題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態 題號 狀態
1100 ac 1101 ac 1102 ac 1103 ac 1104 ac 1105 ac 1106 ac 1107 ac 1108 ac 1109 ac
1110 ac 1111 ac 1112 ac 1113 ac 1114 ac 1115 ac 1116 ? 1117 ac 1118 ac 1119 ac
1120 ? 1121 ac 1122 ac 1123 ? 1124 ac 1125 ac 1126 ac 1127 ac 1128 ac 1129 ac
1130 ac 1131 ac 1132 ac 1133 ac 1134 ac 1135 ac 1136 ac 1137 ac 1138 ac 1139 ac
1140 ac 1141 ac 1142 ac 1143 ac 1144 ac 1145 ac 1146 ac 1147 ac 1148 ac 1149 ac
1150 ac 1151 ac 1152 ac 1153 ac 1154 ac 1155 ac 1156 ac 1157 ac 1158 ac 1159 ac
1160 ac 1161 ac 1162 ac 1163 ac 1164 ac 1165 ac 1166 ac 1167 ac 1168 ac 1169 ac
1170 ac 1171 ac 1172 ac 1173 ac 1174 ac 1175 ac 1176 ac 1177 ac 1178 ? 1179 ?
1180 ac 1181 ac 1182 ac 1183 ac 1184 ac 1185 ac 1186 ac 1187 ac 1188 ac 1189 ac
1190 ac 1191 ac 1192 ? 1193 ac 1194 ac 1195 ? 1196 ac 1197 ac 1198 ac 1199 ac
<script type="text/javascript"> detail 1100,"勇闖黃金十二宮……白羊宮" </script>
Prob 1100: 勇闖黃金十二宮……白羊宮 回頁首 四件圣衣分別計算,相加即可。把一件圣衣上所有洞的破損程度值分成和盡可能接近的兩組,則和較大的一組的和就是修補這件圣衣所需的最少時間。這個時間的求法可參看1017 石子歸并問題。<script type="text/javascript"> detail 1101,"勇闖黃金十二宮……金牛宮" </script>
Prob 1101: 勇闖黃金十二宮……金牛宮 回頁首 根據定義,一個數n是牛數的充要條件是它有4個或更多的質因數。因此將它分解質因數即可。注意可能有一個因數可能大于sqrt(n)。<script type="text/javascript"> detail 1103,"勇闖黃金十二宮……巨蟹宮" </script>
Prob 1103: 勇闖黃金十二宮……巨蟹宮 回頁首 這道題與1084是極為類似的。同樣是搜索每個質因數的指數,用同樣的公式計算約數個數,用同樣的不等式剪枝。此題與1084相比較簡單的一點是,雖然在試乘的過程中數值會超出longint甚至cardinal的范圍,但不必使用高精度,用int64即可。<script type="text/javascript"> detail 1107,"勇闖黃金十二宮……天蝎宮" </script>
Prob 1107: 勇闖黃金十二宮……天蝎宮 回頁首 氣死人的題:題目中說n<=10,但測試數據中的n沒有超過6的!!!所以,樸素搜索即可,不必加任何剪枝。<script type="text/javascript"> detail 1113,"反約瑟夫" </script>
Prob 1113: 反約瑟夫 回頁首 用F[n]表示n個人圍一圈時,最后剩下的人的編號。顯然F[1]=1。當我們知道F[i-1]時,我們可以算出F[i]:首先數過去M個人,然后剩下的人相當于在玩i-1個人的約瑟夫游戲,所以最后剩下的人在這i-1個人中的次序應該是F[i-1]。因此F[i]=(M+F[i-1]) mod i,當結果為0時修正為i。
  有了這個復雜度為O(n)式子,我們便可以從小到大枚舉M了。<script type="text/javascript"> detail 1117,"分子量計算" </script>
Prob 1117: 分子量計算 回頁首 一點竅門:在整個式子左邊加一個'(',倒著遞歸計算比較方便。<script type="text/javascript"> detail 1119,"范式" </script>
Prob 1119: 范式 回頁首 這道題顯然要用構造法。首先我們要知道哪些數的結果是Yes,最好能證明,還需要知道結果是No的數反例如何構造。
  做個實驗就會發現,對大于等于4的偶數,反例遍地都是,而大于等于7的奇數,反例不多,但是存在。
  先看簡單的情況,即偶數。設n為偶數,則Fan(n)為n組n-1個括號相乘相加。我們只需使第一組括號乘積為負,其它組括號乘積全為0即可。于是很容易想出反例:2 3 3...(共n-1個3)。
  再看奇數。仍用上面的思路,但這次要構造一組負積就困難一些了。設n為奇數。令A1=2,則A2至An中,需要有奇數個數大于2,奇數個數小于2。如果小于2的只有1個,假設為A2=1,那么第2組括號將為正值而不為0。于是我們只好令A2=A3=A4=1,而A5至An全部等于3。同樣,大于2的數也不能只有一個,因此n至少等于7。
  那么Yes的情況就只能有2、3、5了。下面試著證明。為書寫方便,不用A1、A2等表示每個數,而用a、b等表示。
  Fan(2)=(a-b)+(b-a)=0>=0是顯然的。Fan(3)=(a-b)(a-c)+(b-a)(b-c)+(c-a)(c-b)=a2+b2+c2-ab-bc-ca=[(a-b)2+(b-c)2+(c-a)2]/2也容易證明。下面給出較難的n=5時的證明: 假設a<=b<=c<=d<=e。令A=(a-b)(a-c)(a-d)(a-e), B、C、D、E的定義類似。
顯然A,C,E>=0,B,D<=0.
比較|A|和|B|:
因為|a-b|=|b-a|,|a-c|>=|b-c|,|a-d|>=|b-d|,|a-e|>=|b-e|
所以|A|>=|B|
所以A+B>=0
同理D+E>=0
又因為C>=0,所以A+B+C+D+E>=0,即Fan(5)>=0恒成立。 <script type="text/javascript"> detail 1121,"Random的問題" </script>
Prob 1121: Random的問題 回頁首 const即可。不要忽略b=0的情況。<script type="text/javascript"> detail 1122,"樂隊" </script>
Prob 1122: 樂隊 回頁首 見加強版1146。<script type="text/javascript"> detail 1133,"難題" </script>
Prob 1133: 難題 回頁首 真是一道難題,要用到棋盤多項式。另外,我把1的個數的上限定為20的時候,RE216了,定為25才AC。

  什么是棋盤多項式?
  棋盤多項式是一個關于x的多項式,式中n次項的系數表示在棋盤上放置n個棋子,且無任意兩個棋子同行或同列(稱“互不沖突”)的方法數。例如在棋盤上,放置0個棋子且互不沖突的方法數為1,放置1個棋子且互不沖突的方法數為3,放置2個棋子且互不沖突的方法數為1,放置3個棋子且互不沖突的方法數為0,那么棋盤的棋盤多項式就是x2+3x+1,記作R()=x2+3x+1。

  怎樣計算棋盤多項式?
  顯然對于空棋盤,R( )=1。對非空棋盤A,任意選定一個格子g,設從A中去掉g及所有與g同行或同列的格子后剩余的棋盤為A1,從A中僅去掉g后剩余的棋盤為A2,則R(A)=xR(A1)+R(A2)。
  例如:R()
  =xR()+R() (此步選定的是左上方的格子)
  =x(xR( )+R( ))+xR( )+R()
  =x(x+1)+x+x+1
  =x2+3x+1

  棋盤多項式要怎么用?
  想象一個n*n的棋盤,輸入中的1是棋盤的禁區。設禁區的棋盤多項式的i次項的系數為fi。在整個棋盤上放置n個棋子,至少有i個落入禁區的方法數為fi*(n-1)!。由容斥原理,在整個棋盤上放置n個棋子,無一落入禁區的方法數為。因此計算出禁區的棋盤多項式,問題就解決了。當然,需要用高精度。

  復雜度問題!
  在計算有n個格子禁區的棋盤多項式的過程中,總共要計算多少個棋盤的棋盤多項式呢?感覺是2n個。這個復雜度無論是時間上還是空間上都是無法承受的。怎么辦呢?其實什么也不用辦,因為實際上計算的棋盤數不會超過2n/2+1。如果計算棋盤多項式時,每次都選定最上面一行的最左面一格,則這個上限會在棋盤上每列都有至少2個格子,且列與列之間沒有格子同行,且對于任意兩列a、b,a的最高格總是高于b的次高格時達到。假設棋盤共有m列,則在需計算的棋盤中,若第x列的最高格已經不存在了,則第1至x-1列的最高格必然也不存在了。故第1列的最高格存在的棋盤只有1(即20)個,第1列的最高格不存在而第2列的最高格存在的棋盤有21個(因為第1列除去最高格后的部分有存在與不存在兩種可能),前2列的最高格不存在而第3列的最高格存在的棋盤有22個(前2列除去最高格后的部分都可能存在或不存在)……m列的最高格都不存在的棋盤有2m個。也就是說,棋盤總數的上限大約為2m+1。因為對這種棋盤有m<=n/2,故實際計算的棋盤數不會超過2n/2+1。在n<=25時,這個復雜度在時間上是完全可以承受的,在空間上,若給每個棋盤編一個號,使用哈希表存儲棋盤多項式,也沒有問題。

  本題使用了高精度、二進制編號、哈希表等多種算法和數據結構,對編程能力也是一個極好的鍛煉。<script type="text/javascript"> detail 1136,"回家" </script>


Prob 1136: 回家 回頁首 把邊當作點,點當作邊,Dijkstra即可。<script type="text/javascript"> detail 1137,"賭場的規矩" </script>
Prob 1137: 賭場的規矩 回頁首 很明顯,這是一道圖論題。首先構造一個圖:每一個洞作為一個頂點,在某兩個洞之間交換了幾次,就在代表這兩個洞的頂點之間連幾條邊。把最后可能藏有小球的洞對應的頂點叫做可達的頂點,其它頂點叫做不可達頂點。顯然,可達頂點只存在于包含頂點1的連通分支中。以下的討論都是針對包含1號頂點的連通分支。
  現在提出兩個結論:

  結論1:若頂點1到頂點x有一條經過3個或3個以上頂點的路徑,則頂點x是可達的。
  證明:如果依次進行路徑上的邊所代表的操作,則顯然小球最后會在x號洞里。我們只需讓不在這條路徑上的邊(以下稱作“多余邊”)代表的操作全部成為空操作即可。
  而這一點是很容易做到的。以路徑上有且只有3個頂點的情況為例。把這3個頂點分別記作A、B、C。當小球在A洞里時,可以進行B、C之間多余的邊(如果有的話)所代表的操作,這時,這些操作全部為空操作。同理,當小球在B洞里時,可以使A、C之間的多余操作成為空操作;當小球在C洞里時,可以使A、B之間的多余操作成為空操作。與A相連但不與B、C相連的多余邊所代表的操作可以在小球不在A洞時進行,只與B或C相連的多余邊可同理處理。與A、B、C均不相連的多余邊,則可以在任意時刻處理掉。證畢。

  結論2:不可達的頂點至多只有一個。
  證明:由結論1易知,與頂點1的距離(到頂點1的最短路長度)大于1的所有頂點皆可達。對于頂點1本身和與頂點1直接相連的頂點,分以下幾種情況討論:

if 頂點1為孤立頂點 then ?
 不存在不可達的頂點 ?
else if 頂點1在某個環上 then ?
 不存在不可達的頂點 //因為頂點1到環上的頂點必存在經過3個或更多頂點的路徑,頂點1到環外的頂點總可以先走一遍環
else if 與頂點1關聯的邊全部為單邊 then ?
 有且只有頂點1不可達 //因為一旦進行了與頂點1關聯的操作,小球離開了1號洞,它就不可能再回來了
else if 與頂點1關聯的邊中有一組重邊(設這組重邊連接的另一頂點為a) then ?
 if 除重邊(1,a)以外還有重邊與頂點a關聯 or 頂點a在某個環上 then ?
  不存在不可達的頂點 //頂點1到頂點a存在這樣一條路徑:先走重邊(1,a)中的一條,再走a的另一組重邊或環;頂點1到自身存在這樣一條路徑:先走重邊(1,a)中的一條,再走a的另一組或環,再走重邊(1,a)中的另一條;頂點1到與頂點1相連的其它任一頂點b存在路徑1-a-1-b:以上路徑經過的頂點數均>=3
 else ?
  if (1,a)為奇重邊 then 有且只有頂點1不可達 else 有且只有頂點a不可達 //頂點1到與頂點1相連的其它任一頂點b存在路徑1-a-1-b;重邊(1,a)中的所有邊無法成為空操作,因為如果想讓重邊(1,a)中的某一條成為空操作,小球必須離開頂點1和頂點a,而走了就回不來了
else if 與頂點1關聯的邊中有超過一組的重邊(設頂點1與頂點a、b均以重邊相連) then ?
 不存在不可達的頂點 //頂點1到自身存在路徑1-a-1-b-1;頂點1到頂點a存在路徑1-b-1-a;頂點1到頂點b存在路徑1-a-1-b;頂點1到與頂點1相連的其它任一頂點c存在路徑1-a-1-c:以上路徑經過的頂點數均>=3

  有了上面兩個結論以及關于不可達頂點的詳細分析,剩下的工作就是判斷頂點1和與頂點1直接相連的頂點是否在環上了。這一點也不難。為了找到含頂點1的連通分支,我們需要以頂點1為根做一次“灌水法”。實現時用BFS,并記錄從頂點1到每個頂點的路徑上的第2、3個頂點(記作r1、r2)。若在BFS的過程中碰到一條連接兩個已訪問的頂點的邊,則比較這兩個頂點的r1和r2。若r1不同,則頂點1在環上,否則若r2不同,則相同的r1在環上。
  這種算法的核心只是一次灌水法,復雜度為O(n2),題目所給的數據范圍n<=20完全是小菜一碟了。<script type="text/javascript"> detail 1138,"多項式乘法" </script>


Prob 1138: 多項式乘法 回頁首 依次把每項相乘,降冪排序,合并同類項即可。
  數據很弱,我是把每個式子的最大項數定為1000的。如果定為10000,則會MLE;)
  至于輸入中的多余空格的問題,我是寧信其有不信其無。也就多加那么幾條語句而已:)<script type="text/javascript"> detail 1140,"烷烴命名之Step 1" </script>
Prob 1140: 烷烴命名之Step 1 回頁首 顯然這個題是求一棵樹中的最長路。以頂點a為根的子樹中的最長路長度=max{頂點a的所有子樹中最長路長度最大值(最長路不經過a的情況),頂點a的所有子樹深度的最大值與次大值之和+1(最長路經過a的情況)}。注意到根結點有4棵子樹,其余的C有各有3棵子樹,H無子樹,于是可用遞歸輕松解決問題。<script type="text/javascript"> detail 1141,"統計一氯代烷同分異構體數目" </script>
Prob 1141: 統計一氯代烷同分異構體數目 回頁首 還是DP。把連有Cl原子的C原子看作樹根,那么它有3棵子樹。設f[n]表示一氯n烷的同分異構體數目,則有狀態轉移方程

其中H為重組合的符號,表示從n個元素中可重復地取出m個的組合數,(用隔板原理想一下就會明白)。狀態轉移方程的第三項當且僅當n mod 3=1時存在。
  本題的結果不超過qword范圍,因此不必使用高精度。<script type="text/javascript"> detail 1142,"蟲食算" </script>


Prob 1142: 蟲食算 回頁首 只用最簡單的搜索方法即可:從右到左依次搜索每一列中的每一個字母代表的數字(這樣可以充分利用進位的信息)。每假設出一個字母的值,就檢查一下它左邊的每一列是否可能成立。檢查也很簡單:如果一列的3個字母的值都已確定,那么判斷一下它在進位和不進位兩種情況下是否有可能成立;如果僅確定了兩個字母,那么計算一下剩下的那個字母在進位和不進位兩種情況下分別應取的值,如果這兩個值都已被別的字母占用,則剪枝;在檢查最左一列時,如果發現它向“第0位”進位了,也可以剪枝。
  為了避免試探字母取值時屢次碰到已用過的字母,除了用布爾數組used記錄每個數字是否用過(檢查時要用)外,還可以建立一個鏈表,其中存儲當前尚未用過的數字。這樣會在一定程度上提高程序的速度。
  但奇怪的是,對于某一個字母,按怎樣的順序試探它的值對程序效率有很大的影響。從2004年11月NOIP到2005年4月,我一直是按從0到n-1的升序試探的,最快的程序通過全部10個數據也需要2s左右的時間。借鑒了一下靜默守護的程序,發現他是按0,n-1,1,n-2,2,n-3...的順序試探的,速度比我的快得多。我又編了兩個程序,分別用n-1到0的降序和隨機序進行試探,發現降序的程序比靜默守護的還快,隨機序稍慢。下面是某電腦上我編的三個程序的運行時間比較: 試探順序 運行時間
升序 1.970s~1.987s
降序 0.011s~0.016s
隨機序 1.026s~1.042s
  現在我無法從理論上說明逆序和隨機序哪個更優。我感覺,既然總搜索量是一定的,影響出解速度的因素只是解在搜索樹中的位置,也就是說,無論哪種試探順序,出解時間的期望都是一樣的,只是本題的測試數據可能恰好適于降序試探。專門用來對付降序的測試數據也是可能存在的。所以,我比較提倡隨機序。在壓縮包中,我提供了升序(1142up.pas)、降序(1142down.pas)和隨機序(1142rnd.pas)三個程序。<script type="text/javascript"> detail 1143,"二五語言-版本2" </script>
Prob 1143: 二五語言-版本2 回頁首 以下敘述中,“單詞”均指合法單詞。
  舉個例子說明:若為單詞轉編碼,如求單詞ACF……的編碼,則設一累加器,先累加以AB開頭的單詞的個數,再累加以ACB開頭的單詞的個數(這個數為0,但若已知6個字母的位置,B拐到了第2行,則可能不為0),再累加以ACD開頭的單詞的個數,再累加以ACE開頭的單詞的個數……最后加1即得答案。若為編碼轉單詞,如求第n個單詞,同樣設一累加器s,先累加以AB開頭的單詞的個數,若s>=n了,說明第二個字母就是B,否則繼續累加以AC開頭的單詞的個數……直到s>=n,這樣第二個字母就確定了。將最后一次累加的數減去,用類似的方法確定第三、第四……個字母,直至結束。
  現在的問題是:如何求出以某給定序列開頭的單詞的個數?這個問題是用記憶化搜索解決的。用f[a,b,c,d,e](5>=a>=b>=c>=d>=e>=0)表示把前a+b+c+d+e個字母填入第1行的前a個格,第2行的前b個格……第5行的前e個格,且已經確定位置的字母各就各位時可能的單詞數,那么f[0,0,0,0,0]就表示以給定序列開頭的單詞數。下面以求以AC開頭的單詞數為例說明遞歸求f數組的方法:
  • 第一層遞歸安置字母A。因其位置已固定,故f[0,0,0,0,0]=f[1,0,0,0,0],進入第二層遞歸計算f[1,0,0,0,0]。
  • 第二層遞歸安置字母B。B的位置尚未固定,于是枚舉所有合法位置(合法位置指左、上方均已填有字母的位置,認為第0行與第0列均已填滿。此例中為12、21),分別進入第三層遞歸計算f[2,0,0,0,0](這個值等于0,下面會討論其原因)與f[1,1,0,0,0]。f[1,0,0,0,0]即等于這二者之和。
  • 第三層遞歸安置字母C。這層遞歸的過程與第一層遞歸類似。更深層遞歸的過程與第二層遞歸類似。若在某一次遞歸中,需要計算的f值已經算出,則不必再遞歸下去,直接退出即可。
  因為每次計算單詞個數時給定的序列都不同,所以每次都必須從頭遞歸。

  程序的實現用了一點小技巧。上文中說,B的合法位置有兩個,分別是12和21。但實際上,12這個位置已經被字母C占據,只是在第二次遞歸時程序并不知道這一點。請看程序第26行中的這一條件:len[x[c]]+1=y[c]。如果某個位置已固定的字母的位置已被別的字母占據,那么這個條件就為假,從而求出的單詞數就成了0。當然,可以在遞歸之前把已被占據的位置做上標記,但因為需要搜索的狀態總數本來就不多(只有252種),做標記不會顯著提高時間效率,反而會增加編程復雜度。
  除上段所說的以外,還有幾點可以優化的地方,如以ACB開頭的單詞數可不經搜索直接得到0,再如當遞歸深度超過了所有已固定的字母時,可直接利用版本1中的DP獲得結果,而不須遞歸下去。然而,不加這些優化的算法的復雜度已經很低了(最壞情況下為25(25個位置)*25(每個位置可能放25個字母)*252(記憶化搜索的狀態總數)*5(每個狀態最多有5個合法位置)=787500),這些優化都顯得不太值得。<script type="text/javascript"> detail 1144,"二五語言-版本1" </script>


Prob 1144: 二五語言-版本1 回頁首 以下敘述中,“單詞”均指合法單詞。
  用count[a,b,c,d,e](5>=a>=b>=c>=d>=e>=0)表示把前a+b+c+d+e個字母填入第1行的前a個格,第2行的前b個格……第5行的前e個格后,剩下字母的填法數。count數組可用DP計算,其思路為:如果某一行已填的字母數少于上一行已填的字母數(可認為第0行填了5個字母),那么第a+b+c+d+e+1個字母就可以填到這一行的下一個位置。具體實現可參考程序中的calcount過程。
  有了這個數組,解決問題就簡單了:
  • 若為單詞轉編碼,則依次處理每個字母,累加把它放在給定單詞中它所在位置以前的所有合法位置(指左、上方均已填有字母的位置,認為第0行與第0列均已填滿)可構造出的單詞數,最后把結果加1。比如,若A,B,C三個字母分別放在11,21,31三個位置,則在處理B時,累加將其放在位置12時的單詞數(即count[2,0,0,0,0]);處理c時也累加將其放在位置12時的單詞數(即count[2,1,0,0,0]),但不能累加把它放在位置22時的單詞數(因為如果這樣位置12的字母將大于位置22的字母,不合法)。
  • 若為編碼轉單詞,則依次枚舉每個字母可能的位置并累加這時產生的單詞數。若某時刻累加和超過或等于了編碼,則當前處理的字母應放的位置就找到了,減去最后一次累加的數值,繼續處理下一個字母,直至結束。
<script type="text/javascript"> detail 1145,"敲七-版本4" </script>
Prob 1145: 敲七-版本4 回頁首 把與7有關的n位數分成兩種:
  • 不含數字7,但是7的倍數的n位數。用rem[d,r]表示不含數字7且除以7除r的d位數的個數。易寫出如下偽代碼: rem[0,0]:=1;for i:=1 to 6 do rem[0,i]:=0;for d:=1 to n dofor i:=0 to 6 dofor j:=0 to 9 except 7 do //j表示第d位數inc(rem[d,(i*10+j) mod 7],rem[d-1,i]);  不含數字7,但是7的倍數n位數的個數就是rem[n,0]。
  • 含數字7的n位數。顯然這一類數的總數為(其中i表示數字7的個數)。
<script type="text/javascript"> detail 1146,"樂隊(加強版)" </script>
Prob 1146: 樂隊(加強版) 回頁首 首先把一盤CD裝不下的歌全部踢掉!!!

  剩下的工作就是DP了。用f[i,j]表示在前i首歌中選出j首在CD上占的最小時間。這里說的時間包括除最后一盤外的CD上浪費的時間。f[i,j]=min{f[i-1,j],sum(f[i-1,j-1],第i首歌的長度)}。這里的sum的含義是:如果最后一盤CD剩下的時間正好可以放下第i首歌,那么直接相加即可,否則要再加上最后一盤CD剩下的時間(這些時間已被浪費了)。找一個最大的j使f[n,j]<=t*m,這個j就是答案。
  f數組可做成滾動數組。這個算法的復雜度為O(n2),絕不會超時。<script type="text/javascript"> detail 1147,"戰略游戲-版本2" </script>


Prob 1147: 戰略游戲-版本2 回頁首版本1一樣,本題也可以用樹型DP來解。但本題的狀態設計比版本1要復雜一些。具體來說,有以下三個狀態:
  • need[n,0]:表示以n為根的子樹中,除n以外的結點全被了望到,而只有n本身沒有被了望到所需的最少士兵數;
  • need[n,1]:表示以n為根的子樹中的全部結點都被了望到,但n結點上沒有士兵時所需的最少士兵數;
  • need[n,2]:表示以n為根的子樹中的全部結點都被了望到,且n結點上有士兵時所需的最少士兵數。
  每個結點三個need值的計算,還要分葉子結點與非葉子結點來討論。
  • 對葉子結點i:顯然,need[i,0]=0,need[i,1]=maxint(maxint表示不可能),need[i,2]=1。
  • 對非葉子結點i(用j表示它的任一個兒子):
    • need[i,0]的計算:因為結點i本身不可以被了望到,所以它的任何一個兒子結點上都不可以放士兵,即need[i,0]應該等于所有need[j,1]之和。若有某個need[j,1]為maxint,那么need[i,0]亦為maxint。
    • need[i,1]的計算:因為結點i本身沒有士兵,所以它的每個兒子都必須被i的子樹中的結點上的士兵了望到,而且至少一個兒子結點上有士兵。也就是說,need[i,1]應等于所有min{need[j,1],need[j,2]}之和,只是在所有兒子都滿足need[j,1]<need[j,2]時,需要往need[i,1]上加上最小的一個need[j,2]-need[j,1]。
    • need[i,2]的計算:因為結點i本身就有士兵了,所以它的兒子結點怎樣都可以,即need[i,2]等于所有min{need[j,0],need[j,1],need[j,2]}之和。
  設根結點為root,則min{need[root,1],need[root,2]}就是答案。<script type="text/javascript"> detail 1148,"救護傷員" </script>
Prob 1148: 救護傷員 回頁首 雖然數據范圍小得搜索都能過,但我還是要講一下求X、Y頂點數相等的二分圖最大權匹配(也叫最佳匹配)的KM算法

  KM算法是通過給每個頂點一個標號(叫做頂標)來把求最大權匹配的問題轉化為求完備匹配的問題的。設頂點Xi的頂標為A[i],頂點Yi的頂標為B[i],頂點Xi與Yj之間的邊權為w[i,j]。在算法執行過程中的任一時刻,對于任一條邊(i,j),A[i]+B[j]>=w[i,j]始終成立。KM算法的正確性基于以下定理:
  若由二分圖中所有滿足A[i]+B[j]=w[i,j]的邊(i,j)構成的子圖(稱做相等子圖)有完備匹配,那么這個完備匹配就是二分圖的最大權匹配。
  這個定理是顯然的。因為對于二分圖的任意一個匹配,如果它包含于相等子圖,那么它的邊權和等于所有頂點的頂標和;如果它有的邊不包含于相等子圖,那么它的邊權和小于所有頂點的頂標和。所以相等子圖的完備匹配一定是二分圖的最大權匹配。
  初始時為了使A[i]+B[j]>=w[i,j]恒成立,令A[i]為所有與頂點Xi關聯的邊的最大權,B[j]=0。如果當前的相等子圖沒有完備匹配,就按下面的方法修改頂標以使擴大相等子圖,直到相等子圖具有完備匹配為止。
  我們求當前相等子圖的完備匹配失敗了,是因為對于某個X頂點,我們找不到一條從它出發的交錯路。這時我們獲得了一棵交錯樹,它的葉子結點全部是X頂點。現在我們把交錯樹中X頂點的頂標全都減小某個值d,Y頂點的頂標全都增加同一個值d,那么我們會發現:

  • 兩端都在交錯樹中的邊(i,j),A[i]+B[j]的值沒有變化。也就是說,它原來屬于相等子圖,現在仍屬于相等子圖。
  • 兩端都不在交錯樹中的邊(i,j),A[i]和B[j]都沒有變化。也就是說,它原來屬于(或不屬于)相等子圖,現在仍屬于(或不屬于)相等子圖。
  • 一端在交錯樹中,另一端不在交錯樹中的邊(i,j),它的A[i]+B[j]的值有所減小。也就說,它原來不屬于相等子圖,現在可能進入了相等子圖,因而使相等子圖得到了擴大。
  現在的問題就是求d值了。為了使A[i]+B[j]>=w[i,j]始終成立,且至少有一條邊進入相等子圖,d應該等于min{A[i]+B[j]-w[i,j]|Xi在交錯樹中,Yi不在交錯樹中}。<script type="text/javascript"> detail 1151,"拼湊春聯" </script>
Prob 1151: 拼湊春聯 回頁首 按每相鄰兩個字母是否相同可以把長度為7的春聯分為27-1=64類,設第i類有count[i]句,則答案為所有的C(count[i],2)之和。<script type="text/javascript"> detail 1155,"拯救Angel行動" </script>
Prob 1155: 拯救Angel行動 回頁首 這個題是典型的分層圖BFS。在最壞情況下,因為鑰匙有10種,故可以用0至1023這1024個數對應已取得的鑰匙的1024種狀態,把圖分成1024層。這個圖并不大,只有15*15*1024=230400個結點。因為從一個結點出發只有4種走法,所以完全不必擔心TLE。<script type="text/javascript"> detail 1156,"傳球問題" </script>
Prob 1156: 傳球問題 回頁首 不要受“高二數學改編”誤導而想排列組合,這個題要用DP。用a[i]表示傳了i次后球又回到甲手里的傳法總數,b[i]表示傳了i次后球到了乙手里的傳法總數,當然,b[i]也表示傳了i次后球到了丙、丁等人手里的傳法總數,因為乙、丙、丁等人的地位是一樣的。很容易寫出狀態轉移方程: a[i]=(p-1)*b[i-1](倒數第二次只能傳給乙、丙等p-1個人)
b[i]=a[i-1]+(p-2)*b[i-1](倒數第二次或者傳給甲,或者傳給丙、丁等p-2個人)
顯然a[1]=0,b[1]=1。   但是,用這種方法遞推太慢了,n取最大值231-1時顯然要超時。換一種思路:假設要傳i+j次,考慮傳了i次以后的情況,列出如下方程: a[i+j]=a[i]*a[j]+(p-1)*b[i]*b[j](傳了i次后,球可以又回到甲手里,也可以在其他p-1個人手里)
b[i+j]=a[i]*b[j]+b[i]*a[j]+(p-2)*b[i]*b[j](傳了i次后,球可以在甲手里,也可以在乙手里,也可以在其他p-2個人手里)
同樣,a[1]=0,b[1]=1。   用這組方程可以很容易設計出O(logn)的算法。<script type="text/javascript"> detail 1157,"愚蠢的教官" </script>
Prob 1157: 愚蠢的教官 回頁首 在以下的敘述中,sum(a,b)表示從a加到b的和(a<=b,且a,b為整數)。
  我們把小于等于(n+1) div 2的號叫做小號,其余的號叫做大號。顯然,小號都排在奇數位上,大號都排在偶數位上。那么,在絕對值和的計算過程中,不在端點的大號都做了兩次被減數,不在端點的小號都做了兩次減數,而在端點大號或小號的則只做了一次被減數或減數。而隨n的奇偶性不同,最后一個數是大號還是小號也不同,也就是說最后一個數的“功能”(被減數還是減數)不同。于是我們對n的奇偶性分情況討論。
  當n是奇數時,隊首的1少做了一次減數,隊尾的n div 2+1也少做了一次減數,于是絕對值和=2(sum(n div 2+2,n)-sum(1,n div 2+1))+1+n div 2+1。這個式子最終可以化簡到n*(n-1) div 2。
  用last(n)表示n個人的隊列中隊尾的編號。當n是偶數時,隊首的1少做了一次減數,隊尾的last(n)少做了一次被減數,于是絕對值和=2(sum(n div 2+1,n)-sum(1,n div 2))+1-last(n)。剩下的工作就是求last(n)了。當n為奇數時,顯然last(n)=n div 2+1。當n為偶數時,把所有的小號全部去掉,大號全部減去n div 2,發現現在的隊列變成了n div 2個人時的隊列,即last(n)=n div 2+last(n div 2)。這樣一直遞歸下去,直到n變成奇數為止。
  算法復雜度僅為O(logn)。<script type="text/javascript"> detail 1159,"半數集" </script>
Prob 1159: 半數集 回頁首 本題我采用的是DFS算法。判重的問題我是這樣解決的:對每一個數串,按某種規則將它劃分成若干個數。如果這些數恰好是搜索到這一個數串時棧中的數,那么就把答案加1,否則就不加。這樣就可以保證相同的數串只算一個。這個規則就是:從后往前劃分,使每個數都盡可能大。當然,每個數的開頭都不能是0。
  舉個例子:假設輸入的數是99,那么按照規則,數串123499的劃分應該是12 34 99。搜到這個數串時,如果棧中的數恰好就是12和34,那么答案加1;如果棧中的數是1 2 34或者1 2 3 4,則不加1。<script type="text/javascript"> detail 1163,"煉金術" </script>
Prob 1163: 煉金術 回頁首 題目敘述有一點毛病:可以不把金子煉成別的金屬而直接送出境。
  算法嘛,就是用Dijkstra求點1到其它各點的最短路,再用Dijkstra求其它各點到點1的最短路,答案便是每個點的兩個最短路長度與單價的一半的和的最小值。不說“即可”了,因為這個Dijkstra要用堆來優化,這個編程挺費事。另外,兩個Dijkstra可以一起做,這樣當某個點的兩個最短路長度都已經求出來時,可以立即更新答案。如果某個Dijkstra中堆頂元素的最短路長度已經超過了答案,這個Dijkstra就可以停止了。<script type="text/javascript"> detail 1164,"猴子分桃" </script>
Prob 1164: 猴子分桃 回頁首 用ak表示第k個猴子走后剩余的桃子數。我們要求的就是a0和an的最小值。由題意可列出遞推式: ak+1=(ak-1)*(n-1)/n……①  這個遞推式的形式類似等比數列的遞推式,但多一個-1。為了將它整理成標準的等比數列的形式,我們設 bk=ak+x……②讓{bk}成等比數列: bk+1=bk*(n-1)/n……③而x是未知數。下面想辦法求x:
  把②式代入③式,得 ak+1+x=(ak+x)*(n-1)/n即 ak+1*n=ak*(n-1)-x……④  同時把①式整理成如下形式: ak+1*n=ak*(n-1)-(n-1)……⑤  比較④⑤兩式,得 x=n-1  回過頭來我們再關注數列{bk}。顯然 bn=b0*[(n-1)/n]n因此 b0 min=nn,bn min=(n-1)n代入②式即求得答案: a0 min=nn-(n-1),bn min=(n-1)n-(n-1)<script type="text/javascript"> detail 1165,"切蛋糕" </script>
Prob 1165: 切蛋糕 回頁首   先討論比較簡單的直線的情況。若直線沒有切著蛋糕,則答案顯然。下面只討論直線切著蛋糕的情況。觀察一下右面這個圖,圖中直線的上方有2塊蛋糕,而下方有3塊。每一塊的外邊(這個“邊”不念輕聲)都有一個特征:左轉,比如下方較大的那一塊,它的外邊是A-4-5-F,是左轉的。每一條內邊也有一個共同特征:右轉。還以下方較大的那一塊為例,它的內邊是E-12-13-B,是右轉的。恰好,每一塊蛋糕都有且僅有一個外邊,所以我們只需統計一下在直線上下方各有多少段左轉的邊界,第一問就已經解決了。
  這一結論也適用于圓外蛋糕數目的統計,但對于圓內就不適用了,因為圓內的每塊蛋糕沒有“外邊”、“內邊”的概念,它的邊數不固定,每條邊的旋轉方向也不固定。比如左面那個圖,圓內較大的一塊竟有3條邊,且左邊不轉,下邊和右邊都是右轉。然而很不幸,題目問的恰恰就是圓內的塊數。我們必須另辟蹊徑。
  剛才討論直線的情況時,我們沒有充分利用內邊的信息。其實,內邊的條數與蛋糕被切成的塊數也是有直接關系的。如果一塊蛋糕有n個內邊,那么它的切口就有n+1個,如下方較大的一塊有1個內邊,它的兩個切口是AB和EF。而與這兩個切口相連的上方的塊一定不同!這暗示我們,直線一邊的內邊的數目,與另一邊的蛋糕塊數有某種聯系!我們從原始的情況來想。首先想象一塊蛋糕被直線一分為二。然后,蛋糕的上方收縮,整個蛋糕成為“凹”字形,直線恰好經過它的兩條“腿”。這時,直線下方多了一條內邊,上邊多了一塊蛋糕。于是我們發現了,直線上方的蛋糕塊數等于下方內邊數加1,下方的蛋糕塊數等于上方內邊數加1。這個算法也適用于求圓內的蛋糕塊數,因為圓外蛋糕的內邊同樣具有“右轉”的特征。還是看一下左邊的圖,由于圓外上方的那一塊有一條內邊(點8附近),故圓內的蛋糕塊數為2。
  剩下的問題就是判斷線段是否與直線相交、線段是否進入圓或從圓中出來的幾何問題了。相信這些你都會的:)<script type="text/javascript"> detail 1166,"背包問題" </script>
Prob 1166: 背包問題 回頁首 把能取得的若干連續重量用區間存儲,比如重量3,4,5可用區間[3,6)表示。閱讀下文,你將體會到用半開半閉區間與用閉區間相比的優越性。
  開始時解集中只有一個區間[0,1)。然后處理每一個物體。設當前處理的物體重量為w,那么把原來解集A中的每個區間都向右平移w得到一個集合B,合并A和B得到新的解集。合并時選用歸并排序,這樣可使原解集與新解集都保持有序性。當即將加入新解集的區間[c,d)與新解集最后一個區間[a,b)重疊或恰好相連(其充要條件為c<=b)時,將兩區間合并為一個區間。這里體現了半開半閉區間的優越性之一:若用閉區間,則條件為c<=b+1,浪費一次加法運算。當所有物體都處理完畢后,累加每個區間中整數的數目。這里體現了半開半閉區間的另一點優越性:半開半閉區間[a,b)中的整數個數為b-a,而閉區間[a,b]中的整數個數為b-a+1,相比之下,半開半閉區間又節省了一次加法運算。因總重量不能為0,故答案為累加和減1。
  思路已經有了,在具體實現時又遇到這樣一個問題:在某一時刻區間最多會有多少呢?理論上講,若物體數為n,則總重量數為2n(包括0),在最壞情況下,這些重量都不連續,就需要2n個區間。但在最大規模隨機數據的情況下,實驗得知區間數一般不超過200000個,因此題目所給的內存是足夠的。
  以上算法還有一點值得優化。這道題不能使用1017題那樣的樸素算法,原因就在于重量數太多,而區間恰恰解決了這一矛盾。為了讓區間的優勢發揮得更充分,我們在執行上述算法之前先對所有物體的重量進行一次排序,然后從小到大依次處理。這樣,在計算過程中,重量就會最大程度地向輕的方向聚集,區間也會最大程度地合并,時間、空間需求都大大降低:未經優化的算法用時6.9s,而優化后的算法僅用2.3s;實驗發現,在輸入最大規模的隨機數據時,優化后的算法在運行過程中區間的最大數目僅為幾十至幾百個,遠遠小于優化前的十萬個左右。但是,當物體個數比較少而重量又很分散的情況下,區間數接近物體數的指數函數,如物體數為18,最大重量為100000的情況下,最大區間數仍可超過100000,因此建議仍把最大區間數定在200000左右。<script type="text/javascript"> detail 1167,"城墻防御" </script>
Prob 1167: 城墻防御 回頁首 這道題的算法是O(n*m)的DP。顯然可以用列數作階段。狀態需要好好設計:用2*m表示最后一列屬于第m個凹口(第0個凹口定義為最左邊的空白),用2*m+1表示最后一列左邊已經有了m個凹口,而這一列本身是凸出的。用f[n,m]表示階段n狀態m時的最大適用度和,則顯然有 f[n,m]=min{f[n-1,m-1],f[n-1,m]}+a[i]+b[i],當m為奇數
f[n,m]=min{f[n-1,m-1],f[n-1,m]}+b[i],當m為偶數
  其中,a[i]表示第1行第i列土地的適用度,b[i]表示第2行第i列土地的適用度。若某個f[n,m]對應不可能情況,則其值為負無窮大。f[n-1,m-1]表示最后一列與倒數第二列高度不同的情況,f[n-1,m]表示這兩列高度相同的情況。
  算法不難,但編程上需要在細節上下功夫,盡可能地優化常數系數。下面我將對照程序,說明怎樣盡可能縮短運行時間:
  • a[i]、b[i]這兩個值需要經常用到,因此需對其進行預處理。注意到在狀態轉移方程中,是加a[i]+b[i]還是加b[i]與m的奇偶有關,于是用a[true,i]表示原來的a[i]+b[i],用a[false,i]表示原來的b[i],這樣在j循環中就可以減少一次判斷了。
  • 顯然f[n,*]只與f[n-1,*]有關,因此f應做成滾動數組f[boolean,0..maxm](此處的maxm應為題目中的maxm*2+1)。因為在j循環中要屢次訪問f[odd(i),*]和f[not odd(i),*],因此用b1、b2兩個布爾變量暫存odd(i)和not odd(i)的值,以減少odd函數的調用次數。
  • DP算法的一個特點是它計算的信息量很大,而算出來的信息往往有一部分是沒有用的。如右圖,整個矩形表示本題中DP計算的總信息量,而其中只有藍色部分是有用的,這部分還占不上總信息量的一半。因此有必要僅計算藍色部分所代表的信息,這就是j循環前計算的k、l的用途。這一操作可以顯著提高程序效率。
  • 繼續觀察狀態轉移方程。方程中有求較小值這一步,用程序中的方法做而不另外設一個臨時變量,既可以省一個變量,在某些情況下還可以省去一次賦值。這樣做導致了j循環必須用downto而不能用to,請仔細思考一下為什么。
  • 再介紹一個好東西:{$R-},即關閉range check。在保證程序不會發生數組越界等錯誤時,range check是既多余又耗時的。加上這一編譯開關后,程序的運行時間縮短了一半以上,提交后用2.3s AC(雖然這個速度仍不是很快)。P.S.其實還有一個編譯開關{$Q-},即關閉overflow check,也能提高程序速度。
  • <script type="text/javascript"> detail 1169,"廣告印刷" </script>
    Prob 1169: 廣告印刷 回頁首 顯然當廣告面積最大時,要有一個建筑物被刷滿。枚舉這個建筑物的編號,然后尋找把這個建筑物刷滿的廣告向左、向右最遠分別能延伸到什么位置。
      用l[i]、r[i]分別表示建筑物i被刷滿時廣告向左、向右最遠能延伸到的位置。從左到右依次計算每個l[i]:初始時令l[i]=i,然后while h[i]<=h[l[i]-1] do l[i]:=l[l[i]](h[i]表示第i個建筑物的高度)。r[i]的計算方法類似,只是方向為從右向左。為計算方便可令h[0]=h[n+1]=0。答案就是所有h[i]*(r[i]-l[i]+1)中的最大值。
      由于上述的while循環類似于并查集中的路徑壓縮,故本算法的復雜度可近似認為是O(n),只是常數系數較大些。

      P.S.我在競賽時,看到n的最大值是1000000,就不自覺地想O(nlogn)復雜度的算法,像靜態排序二叉樹,像堆,等等等等……花了兩個小時,編了五個程序,換來一個TLE:(
      后來想到這個算法,5分鐘編完程,2.2s AC。當時,我覺得這個算法就是O(n)的,看到用時這么長,感覺很奇怪。后來證明復雜度為O(n)時遇到了困難,才發現還有個常數系數在搗鬼。<script type="text/javascript"> detail 1170,"一進制計數" </script>


    Prob 1170: 一進制計數 回頁首 用unary[n]表示n的一進制表達式的最短長度。這個表達式的最后一步可以是加,也可以是乘。如果是加的話,那么幾乎肯定有一個加數是1(這個我沒有證明);如果是乘的話,則需要枚舉n的約數i(大于1,小于等于trunc(sqrt(n)))。由此得狀態轉移方程:unary[n]=min{unary[n-1]+1,unary[i]+unary[n div i]}。
      下面解釋一下為什么我認為“如果最后一步是加,則幾乎肯定有一個加數是1”:整個unary數組的總趨勢是增加的,而且越是在開頭,增加得越快,較往后的位置基本在同一數值附近擺動。更為甚者,開頭5個元素恰好是1,2,3,4,5,增長得不能再快了(因為unary[n+1]-unary[n]<=1)。那么unary[1]+unary[n-1]<=unary[2]+unary[n-2]<=unary[3]+unary[n-3]<=unary[4]+unary[n-4]<=unary[5]+unary[n-5],而這一串不等式中,不僅所有的<=全取等號的可能性微乎其微,而且在一般情況下,unary[5]+unary[n-5]比unary[1]+unary[n-1]要大不少。因此可近似認為,若unary[i]+unary[n-i](i<=n-i)中的i再增大,由于unary[i]的增長速度快于unary[n-i]的下降速度,這個式子的值是不會變得小于unary[1]+unary[n-1]的。實踐也證明了這一點。
      但是,如果對于每一個n,從2至trunc(sqrt(n))依次檢驗每個數是否為n的約數,則算法復雜度為O(n3/2),運行將花費20~30秒,嚴重超時。于是換一種思路:給每個數開一個棧(當然也可以用隊列)。假設當前處理到n,則i就在i的倍數中不小于n且最小的那一個的棧中。處理n(n>=2)時,首先令unary[n]=unary[n-1]+1,然后對于n的棧中的每一個元素f,(1)檢查unary[f]+unary[n div f]是否小于當前的unary[n],若小于則更新;(2)將f從n的棧中取出,放到n+f的棧里。我的程序中,棧是用數組模擬的鏈表實現的。另外,每一個數i不必在開始時就放到i的棧里,可等到處理到sqr(i)時再放進sqr(i)的棧,因為在此之前,若i在n的棧里,則若n=i,顯然i無用,若n>i,因為n div i<i,故i也無用。
      這種改進算法避免了檢驗一個數是否是另一個數的約數,只用了2s多就AC。<script type="text/javascript"> detail 1171,"最大獎勵" </script>
    Prob 1171: 最大獎勵 回頁首 呼呼,思路比程序還長,有些地方還顯得很突兀,不知怎么能想到。我問了21天,終于搞懂了。謝謝Wanght_1的整理。

      此題的DP用逆推。至于為什么用逆推,下文會講。用F[i]表示第i題以后的題的最大總得分(i就是階段)。狀態轉移方程為:
        F[n]=0(顯然)
        F[i]=max{F[j]+W[i,j]}(i<j<=n)
      其中,W[i,j]=(S[j]-S[i])*i-T
        S[n]為前n道題的分值和。
      依次計算F[n-1]至F[0],F[0]就是答案。

      用B[i,k]表示階段i的決策k(決策k表示第i題之后的那一段到第k題為止)的結果,則B[i,k]=F[k]+W[i,k]。
      下面尋找n>=k1>k2>i,且S[k1]>S[k2]時,B[i,k1]>=B[i,k2](決策k1不劣于k2)的充要條件:
        B[i,k1]>=B[i,k2]
      <=> F[k1]+(S[k1]-S[i])*i-T>=F[k2]+(S[k2]-S[i])*i-T
      <=> F[k2]-F[k1]<=(S[k1]-S[k2])*i
      <=> (F[k2]-F[k1])/(S[k1]-S[k2])<=i
      令g(k1,k2)=(F[k2]-F[k1])/(S[k1]-S[k2]),則B[i,k1]>=B[i,k2] <=> g(k1,k2)<=i
      同理有B[i,k1]<B[i,k2] <=> g(k1,k2)>i。

      現在說一下為什么用逆推而不是順推。觀察上面推導過程中兩個紅色的*i。括號外的這個系數是取決于一段開頭的位置的。如果用逆推,這個系數對應的是階段,因此上面的推導能夠進行。如果用順推,這個系數將對應于決策,而我們現在討論的就是不同決策的優劣,這個系數不同將使推導遇到相當大的困難。

      維護一個決策隊列q,使qj遞減,g(qj,qj+1)也遞減。(誰知道怎么想到的呢?)
      依次計算F[n-1]至F[0]。下面考慮計算F[i]時的情形。

  • 目前決策i+1尚未進入隊列。如果i+2<=n且a[i+2]=0,那么顯然i+1不是最優決策,此步不必進行。否則,將決策i+1放到隊列末尾,這樣保證了qj遞減。但對于此時隊尾的三個決策a,b,c,g(a,b)>g(b,c)不一定成立。關注一下g(a,b)<=g(b,c)的情況:
    • 若g(a,b)<=i,則B[i,a]>=B[i,b],決策a不劣于b。
    • 若g(a,b)>i,則g(b,c)>i,B[i,b]<B[i,c],決策c優于b。
    無論哪種情況,b要么不是最優決策,要么是最優決策但不唯一。因此可不斷刪除隊列中倒數第二個決策,直至隊列中決策不足三個或g(a,b)>g(b,c)為止。
  • 現在階段i的可能決策都已經在隊列中了。用a,b表示隊列中的頭兩個決策。若g(a,b)>i,那么a不是最優決策。由于g(a,b)>i-1,因此a在以后也永遠不可能成為最優決策,故可將其刪除。不斷刪除隊首決策直至g(a,b)<=i。這時由于隊列中所有g(a,b)均<=i,故決策的效益是遞減的,所以a為最優決策,F[i]=B[i,a]。
  •   由于隊列在整個過程中只被遍歷一次,故算法復雜度為O(n)。

      上述題解寫于2005年4月。7月,我學到了一種較好地理解上述算法的方法:
      如右圖,把隊列中的決策畫到一個坐標系中(注意,右邊的點位于隊首,左邊的點位于隊尾)。那么,階段i的最優決策應滿足的性質是:過此點作一條斜率(指絕對值,下同)為i的直線,其它所有決策應都位于此直線下方(或直線上)(記作性質①)。下面我們來看如何利用這個圖來理解上述算法:

    • g函數的值就是兩點連線的斜率;
    • 在隊尾刪除的決策,在圖中處于點C的位置,而點C無論何時都不會滿足性質①;
    • 在隊首刪除的決策,在圖中處于點A的位置,由于i遞減,直線的斜率會越來越小,點A以后永遠不會滿足性質①。
      由于算法可以利用斜率來理解,因此它被稱為斜率優化法
      由此也可以理解為什么本題使用逆推:因為斜率對應著階段,而本題中,連續提交的一段題的系數是取決于這一段左端的位置的,左端為階段而右端為決策,所以要用逆推。

      這種算法要求S具有單調性。那么,擴展一下,如果有某些題的分值為負怎么辦呢?可以證明,若某一段(最左一段除外,記作A)的開頭一部分(記作B)總分非正,則這一定不是一種最優劃分。分兩種情況考慮:

    • 若A總分值非正,則把A與上一段合并,這樣不僅省了一次提交(少減了T分),而且減小了A的系數;
    • 若A的總分為正,則這一段中除去B后剩下的一段(記作C)總分仍為正,故可將B移到前一段去,這樣一來減小了B的系數,二來增大了C的系數。總之,若某一段的分值非正,則在這一段前劃分的決策一定不是最優決策。
    因此,可從最后一題向前掃描,累加分值,得到一個正分值后就把累加的這一段壓縮成一道題,并把累加和清零。這樣壓縮以后,除了第一題分值可能非正以外,其余題的分值均為正數,于是便可以使用斜率優化法了。<script type="text/javascript"> detail 1174,"第一章 勝利的逃亡" </script>
    Prob 1174: 第一章 勝利的逃亡 回頁首 用Bellman-Ford求2次最小費用增廣路即可。當然,第一次用Dijkstra會更快些。<script type="text/javascript"> detail 1175,"第二章 文明的復興" </script>
    Prob 1175: 第二章 文明的復興 回頁首 首先將詞典排序,然后從文本中的每個單詞中提取出字母,用二分查找的方法確定這些字母組成的單詞是否在詞典中。用一般的方法對詞典排序可能太慢,可以用一種部分桶排的方法:首先把所有單詞按第一個字母排序,這樣首字母相同的單詞就各自聚到了一起;然后把首字母相同的單詞按第二個字母排序……這樣的排序方法避免了直接比較單詞時,前面的字母都相同帶來的冗余比較,對于此題來說是比較適合的排序算法。
      當然,建trie樹的方法也是可行的。<script type="text/javascript"> detail 1176,"第三章 光榮的夢想" </script>
    Prob 1176: 第三章 光榮的夢想 回頁首 若數列中有這樣兩個數,前面的那個數比后面的大,那么這兩個數就稱為一個逆序對。下面證明最小的交換次數等于數列中逆序對的個數x。
    • x次是足夠的。當數列中存在逆序對的時候,一定有一個逆序對是相鄰的兩個數,否則,整個數列就成了不遞減的,與數列中存在逆序對矛盾。那么,每一次交換一定可以消滅一個逆序對,因此x次是足夠的。
    • x次是必需的。顯然,一次交換最多只能消滅一個逆序對,因此要消滅全部x個逆序對至少需要x次交換。
      現在的問題就是如何高效地求出數列中逆序對的數目。如果先把數列離散化,再用靜態排序二叉樹等樹形數據結構計算每個數之前比它大的數有多少個,然后累加的方法,那么離散化和樹形數據結構的復雜度均為O(nlogn),這個復雜度的算法被執行了兩次。能不能只執行一次呢?答案是肯定的:利用歸并排序。
      回憶一下歸并排序的過程:將數列分為長度相等或相差1的兩段,分別歸并排序后,把兩段看成兩個隊列,每次取隊首元素的較小值,排成一列后就得到有序的數列。觀察排序前的數列,它當中的逆序對可以分為3類:1.兩個數都在前一半的;2.兩個數都在后一半的;3.一個數在前一半,一個數在后一半的。前兩種情況的逆序對的個數可在對那一半的歸并排序中計算,對整個序列的歸并排序的主程序中只需計算第3種情況。注意到歸并過程中,當第二個隊列的隊首元素小于第一個隊列的隊首元素時,第二個隊列的隊首元素與第一個隊列中剩下的元素均構成了逆序對,這時在答案上加上第一個隊列中剩余元素的個數即可。只加這么一條語句,就可以在歸并排序的過程中順便求出逆序對的個數了。<script type="text/javascript"> detail 1177,"第四章 英雄的叛變" </script>
    Prob 1177: 第四章 英雄的叛變 回頁首 這道題的輸入中有前導的0,請加以處理,否則會WA掉!

      下文提到一個數的“前一半”時,若這個數有奇數位,“前一半”均包括中間一位。
      給每個完美數編一個號,使得相鄰的完美數的編號也相鄰。編號方法如下:

    • 若其位數為偶數,則取其前一半,在最高位前一位加1。如456654的編號為1456。
    • 若其位數為奇數,則取其前一半,在最高位上加1。如121的編號為22,92429的編號為1024。
      由編號還原出完美數的方法如下:
    • 若編號首位為1且第二位不為0,則說明對應的完美數為偶數位。去掉首位的1得到的就是完美數的前一半。
    • 若編號首位大于1或前兩位為10,則說明對應的完美數為奇數位。若首位大于1則在首位減1,否則在第2位減1,得到的就是完美數的前一半。
      這樣子算法就很簡單了:求出前一半與a相同的完美數的編號。若a比這個完美數小,則將編號減1。在編號上加上k,還原成完美數,就得到答案。<script type="text/javascript"> detail 1186,"衛星覆蓋" </script>
    Prob 1186: 衛星覆蓋 回頁首 在USACO上學到了一種既簡單又優秀的“切法”,它不僅適用于正方體,同時也適用于長方體。
      這種“切法”依次計算每個長方體不與以前的長方體重疊的部分的體積,相加。計算過程是遞歸進行的。在計算第x個長方體時,調用cal(x-1,第x個長方體)。過程cal(m,長方體C)的內容是:從第m個長方體到第1個長方體依次判斷與長方體C的相交情況,如果都不相交,則累加長方體C的體積;如果有一個長方體與長方體C相交了(不妨設是第y個),就把長方體C在第y個長方體外的部分切成若干塊,對每一塊D執行過程cal(y-1,長方體D)。切的方法是:如果長方體C有在第y個長方體左邊的部分,就把它切下來;然后剩下的部分如果有在第y個長方體右邊的部分,也把它切下來;上、下、前、后四個方向按同樣方法處理。
      這種切法對于一般數據來說效率是很高的。它所遇到的最壞情況就是輸入的長方體都是扁平的板狀,交織成三維網絡(當然,本題輸入的都是正方體,沒有這個問題),這種情況下此法會切出數量級為O(n3)的小長方體(感覺跟離散化了一樣)。但大多數的小長方體不會再被切割了,因此這種切法的最壞時間復雜度可以近似認為是O(n3)。使用線段樹可以把平均時間復雜度降至O(n2logn),但編程復雜度明顯高于切法。<script type="text/javascript"> detail 1190,"個人所得稅" </script>
    Prob 1190: 個人所得稅 回頁首 BS題目把m<=400說成m<=50000嚇唬人。另外,注意輸入中相鄰兩項之間可能有多個空格。<script type="text/javascript"> detail 1196,"01串" </script>
    Prob 1196: 01串 回頁首 利用本題,我來講一下差分約束系統
      所謂差分約束系統,就是求關于xi的由一系列形如xi-xj>=a的不等式組成的不等式組的解。我們構建一個有向圖,圖中的每個結點代表一個xi。對于每個不等式xi-xj>=a,連一條從xj到xi的權為a的邊。不妨設x1=0,那么令xi等于x1至xi的最長路的長度,這樣就得到了一組可行解。若圖中存在正權回路,即對于某些點最長路不存在,那么不等式組無解。
      為什么呢?先看有解的情況。因為若從xi到xj的有一條長為s的路,則要求xj-xi>=s。現在xj與xi的差是xi到xj的最長路的長度,那么對于從xi到xj的任一條路(設其長為s),自然有xj-xi>=s成立。再看無解的情況。設從xi到其本身有一條權為s的正權回路,則要求xi-xi>=s,即0>=s,這顯然是不可能的。
      有了差分約束系統這個武器,解決本題就不難了。用Si表示01串前i位的和,規定S0=0。建一個含有N+1個結點的有向圖,每個結點分別代表S0至SN。然后在圖中連邊:
    • 由于每個數非0即1,故有0<=Si-Si-1<=1(1<=i<=N),即對于每個i,從Si-1向Si連一條權為0的邊,從Si向Si-1連一條權為-1的邊。
    • 對任意長度為L0的子串,其中0的個數不小于A0,不大于B0,即其中1的個數不小于L0-B0,不大于L0-A0。故有L0-B0<=Si-Si-L0<=L0-A0(L0<=i<=N)。故對于每個i,從Si-L0向Si連一條權為L0-B0的邊,從Si向Si-L0連一條權為A0-L0的邊。
    • 對任意長度為L1的子串,其中1的個數不小于A1,不大于B1,即A1<=Si-Si-L1<=B1(L1<=i<=N)。故對于每個i,從Si-L1向Si連一條權為A1的邊,從Si向Si-L1連一條權為-B1的邊。
      求S0到每個結點的最長路,就可以求出每個Si了。01串中的第i位si也就等于Si-Si-1。<script type="text/javascript"> detail 1199,"棋盤分割" </script>
    Prob 1199: 棋盤分割 回頁首 下文中,Σ符號下方的i=1與上方的n均省略。
      首先,我們對均方差的公式σ=sqrt(Σ(xi-x)2/n)進行分析:根號顯然是沒有用的;對于一個特定的棋盤,n為定值,故根號下的分母也是沒有用的。我們繼續整理根號下的分子:
       Σ(xi - x)2
      = Σ(xi2 - 2xix + x2)
      = Σxi2 - 2nx2 + nx2
      = Σxi2 - nx2
      分析整理后的式子:對于一個特定的棋盤,n是定值,x也是定值(等于棋盤每個格子的權之和除以n)。因此,問題的目標就由均方差最小轉化為平方和最小。顯然這個問題可以用動態規劃解決:對于每塊棋盤,我們用四類處理方法:切去左邊的一部分,切去右邊的一部分,切去上邊的一部分,切去下邊的一部分。
      在動態規劃的過程中需要屢次用到某塊棋盤的權和,我們可以用預處理的方法使得不必每次都重新累加:設s[i,j]為棋盤(1,1)-(i,j)的權和(若i=0或j=0,則s[i,j]=0),則棋盤(u,v)-(x,y)的權和為s[x,y]-s[u-1,y]-s[x,v-1]+s[u-1,v-1]。 乥僽儘乕僪僶儞僪椏嬥斾妑亂忋栰僋儕僯僢僋亃24帪娫柍椏儊乕儖憡択嶰榓僼傽僀僫儞僗侓娙扨僉儍僢僔儞僌侓

    總結

    以上是生活随笔為你收集整理的MAIGO的同济题解2的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。