算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每一條指令表示一個或多個操做。node
經常使用的算法:列舉了窮舉搜索、遞歸、回溯、遞推、模擬、分治、貪心、深度優先搜索、廣度優先搜索等幾種較爲經常使用的算法,沒有作過多的描述,一旦給出具體描述,容易使內容加深,產生嚴重學科取向的引導,符合教育部普通高中課程方案的特色,對於這些必需的方法和思想,關鍵不在於學生能不能,而在於教師是否想到,是否有過關注,引起學生對系統方法和思想的思考,重視創建編程思想,強化編程習慣的培養。算法
1.有窮性:一個算法必須老是(對任何合法的輸入值)在執行有窮步以後結束,且每一步均可在有窮時間內完成。編程
2.肯定性:算法中每一條指令必須有確切的含義,不會產生二義性。而且在任何條件下,算法只有惟一的一條執行路徑。數組
3.可行性:一個算法是能行的。即算法中描述的操做是執行有限次運算來實現的。安全
4.輸入:一個算法有零個或多個輸入。網絡
5.輸出:一個算法有一個或多個輸出。數據結構
一般設計一個「好」的算法,應考慮達到如下目標。框架
1.正確性:算法應當知足具體問題的需求。數據結構和算法
2.可讀性:算法主要是爲了人的閱讀與交流,其次纔是機器執行。可讀性好有助於人對算法的理解。函數
3.健壯性:當輸入數據非法時,算法也能適當地作出反應或進行處理,而不會產生莫明其妙的輸出結果。
4.效率與低存儲量需求。
效率指的是算法執行時間。對於同一個問題若是有多個算法能夠解決,執行時間短的算法效率高。低存儲量需求指算法執行過程當中所須要的最大存儲空間。
算法分析的任務是對設計出的每個具體的算法,利用數學工具,討論各類複雜度,以探討某種具體算法適用於哪類問題,或某類問題宜採用哪一種算法。
算法的複雜度分時間複雜度和空間複雜度。時間複雜度是在運行算法時所耗費的時間爲f(n)(即 n的函數)。空間複雜度是實現算法所佔用的空間爲g(n)(也爲n的函數)。稱O(f(n))和O(g(n))爲該算法的複雜度。
程序是對所要解決的問題的各個對象和處理規則的描述,或者說是數據結構和算法的描述,所以有人說,數據結構+算法=程序。
程序設計就是設計、編制和調試程序的過程。程序設計是一門技術,須要相應的理論、技術、方法和工具來支持。就程序設計方法和技術的發展而言,主要通過告終構化程序設計和麪向對象的程序設計兩個階段。
除了好的程序設計方法和技術以外,程序設計風格也很重要。由於程序設計風格會深入影響軟件的質量和可維護性,良好的程序設計風格能夠使程序結構清晰合理,使程序代碼便於維護。所以,程序設計風格對保證程序的質量很重要。
通常來說,程序設計風格是指編寫程序時所表現出的特色、習慣和邏輯思路。程序是由人來編寫的,爲了測試和維護程序,每每還要閱讀和跟蹤程序,所以程序設計的風格整體而言應該強調簡單和清晰,必須能夠理解。能夠認爲,著名的「清晰第一,效率第二」的論點已成爲當今主導的程序設計風格。要造成良好的程序設計風格,主要應注重源程序文檔化。
(1)符號名的命名:符號名的命名應具備必定的實際含義,以便於對程序的功能進行理解。
(2)程序註釋:正確的註釋可以幫助讀者理解程序。
結構化程序設計方法是程序設計的先進方法和工具。採用結構化程序設計方法編寫程序,可以使程序結構良好、易讀、易理解、易維護。結構化程序語言僅使用順序、選擇和循環3種基本控制結構就足以表達出各類其餘形式結構的程序設計方法。
總之,遵循結構化程序的設計原則,按結構化程序設計方法設計出的程序具備明顯的優點。其一,程序結構良好、易讀、易理解和易維護;其二,能夠提升編程工做的效率,下降軟件開發成本。
(1)算法的時間複雜度是指( )。
A.執行算法程序所須要的時間 B.算法程序的長度
C.算法執行過程當中所須要的基本運算次數 D.算法程序中的指令條數
【解析】所謂算法的時間複雜度,是指執行算法所須要的計算工做量。算法的工做量用算法所執行的基本運算次數來度量。
【答案】C
(2)算法的空間複雜度是指( )。
A.算法程序的長度 B.算法程序中的指令條數
C.算法程序所佔的存儲空間 D.算法執行過程當中所須要的存儲空間
【解析】空間複雜度是指執行算法所須要的存儲空間。算法所佔用的存儲空間包括算法程序所佔的空間、輸入初始數據所佔的存儲空間以及算法執行過程當中所須要的額外空間。
【答案】D
(3)算法指的是( )。
A.計算機程序 B.解決問題的計算方法
C.排序算法 D.解決問題的有限運算序列
【解析】所謂算法是指解題方案的準確而完整的描述。對於一個問題,若是能夠經過一個計算機程序在有限的存儲空間內運行有限長的時間而獲得正確的結果,則稱這個問題是算法可解的。但算法不等於程序,也不等於計算方法。
【答案】D
(4)算法能正確地實現預約功能的特性稱爲算法的( )。
A.正確性 B.易讀性 C.健壯性 D.高效率
【解析】針對實際問題設計的算法,人們老是但願可以獲得滿意的結果。但一個算法又老是在某個特定的計算工具上執行的,所以算法在執行過程當中每每要受到計算工具的限制,使執行結果產生誤差。算法與計算公式是有差異的,在設計一個算法時,必需要考慮它的可行性,不然將得不到滿意的結果。
【答案】A
(5)遞歸算法通常須要利用( )來實現。
A.棧 B.隊列 C.循環鏈表 D.雙向鏈表
【答案】A
有一些問題一時難以找到規律或者公式,或者根本沒有規律、公式。這時能夠利用計算機高速運算的特色,使用窮舉來解決。窮舉搜索法是窮舉全部可能情形,並從中找出符合要求的解決。窮舉搜索法全部可能情形,最直觀的是聯繫循環的算法。
例1 找出n個天然數(1,2,3,…,n)中r個數的組合。例如,當n=5,r=3時,全部組 合爲:
5 5 3
5 4 2
5 4 1
5 3 2
5 3 1
5 2 1
4 3 2
4 3 1
4 2 1
3 2 1
total=10 {組合的總數}
[程序]
program zuhe11;
const n=5;
var i,j,k,t:integer;
begin t:=0;
for i:=n downto 1 do
for j:=n downto 1 do
for k:=n downto 1 do
if (i<>j) and (i<>k) and (i>j) and (j>k) then
begin
t:=t+1;writeln(i:3,j:3,k:3);
end;
writeln('total=',t);
end.
這個程序,窮舉了全部可能情形,從中選出符合條件的解,不少狀況下窮舉搜索法仍是經常使用的。
例2 算24點(poi24.pas)。
【題目描述】
幾十年前全世界就流行一種數字遊戲,至今仍有人樂此不疲。在中國咱們把這種遊戲稱爲「算24點」。您做爲遊戲者將獲得4個1~9之間的天然數做爲操做數,而您的任務是對這4個操做數進行適當的算術運算,要求運算結果等於24。
您能夠使用的運算只有:+,-,×,/,您還能夠使用()來改變運算順序。注意:全部的中間結果須是整數,因此一些除法運算是不容許的(例如,(2×2)/4是合法的,2×(2/4)是不合法的)。下面咱們給出一個遊戲的具體例子:若給出的4個操做數是:1、2、3、7,則一種可能的解答是1+2+3×7=24。
【輸入】
只有一行,四個1~9之間的天然數。
【輸出】
若是有解的話,只要輸出一個解,輸出的是3行數據,分別表示運算的步驟。其中第一行是輸入的兩個數和一個運算符和運算後的結果,第二行是第一行的結果和一個輸入的數據、運算符、運算後的結果;第三行是第二行的結果和輸入的一個數、運算符和「=24」。若是兩個操做數有大小的話則先輸出大的。若是沒有解,則輸出「no answer!」
【樣例】
poi24.in poi24.out
1 2 3 7 2+1=3
7×3=21
21+3=24
【算法分析】
計算24點主要應用四種運算。開始狀態有四個操做數,一個運算符對應兩個操做數,因此一開始選擇兩個操做數分別對四個操做符進行循環檢測,每一次運算後產生了新的數,兩個數運算變成一個數,總體是少了一個操做數,因此四個數最終是三次運算。因爲操做的層數比較少(只有三層),因此能夠用回溯的算法來進行檢測,當找到一個解時便結束查找。若是全部的狀況都找事後仍然沒有,則輸出無解的信息。
[程序]
program poi24; {point24}
type arr=array [1..4] of integer;
var i,result,n,len:integer;
d:arr;
r:array [1..3,1..4] of integer;
infile,outfile:text;
procedure print;
var i,j:integer;
begin
assign(outfile,'poi24.out');
rewrite(outfile);
for i:=1 to 3 do
begin
for j:=1 to 3 do
if j<>2 then write(outfile,r[i,j])
else case r[i,j] of
1:write(outfile,'+');
2:write(outfile,'-');
3:write(outfile,'*');
4:write(outfile,'/')
end;
writeln(outfile,'=',r[i,4])
end;
close(outfile);
end;
procedure try(k:integer;d:arr);
var a,b,i,j,l,t:integer;
e:arr;
begin
if k=1 then if d[1]=24 then begin print;halt end else
else begin
for i:=1 to k-1 do
for j:=i+1 to k do
begin
a:=d[i]; b:=d[j];
if a<b then begin t:=a;a:=b;b:=t end;
t:=0;
for l:=1 to k do if (l<>i) and (l<>j) then begin t:=t+1;e[t]:=d[l] end;
r[5-k,1]:=a;
r[5-k,3]:=b;
r[5-k,4]:=-1;
for l:=1 to 4 do
begin
case l of
1:r[5-k,4]:=a+b;
2:r[5-k,4]:=a-b;
3:r[5-k,4]:=a*b;
4:if b<>0 then if a mod b=0 then r[5-k,4]:=a div b
end;
r[5-k,2]:=l;
if r[5-k,4]<>-1 then
begin
e[t+1]:=r[5-k,4];
try(k-1,e)
end
end
end
end;
end;
begin
assign(infile,'poi24.in');
reset(infile);
for i:=1 to 4 do read(infile,d[i]);
close(infile);
try(4,d);
assign(outfile,'poi24.out');
rewrite(outfile);
writeln(outfile,'no answer!');
close(outfile);
end.
X市是一個重要的軍事基地,在這個基地中有一支名爲「彩虹7號」的特別部隊。每一個隊員都有一個固定獨立的編號X(1≤X≤215),他們的職責就是完成部隊交給他們的任務,每一個任務一樣都有固定獨立的編號N(1≤N≤10)。在執行任務的過程當中,爲了保證任務的保密性和隊員的安全,隊員和隊員之間的聯繫將必須由特別部隊所提供的一種保密頻段交互設備進行。
每一個隊員都須要一個身份驗證口令進入系統,因爲隊員所執行的任務都是涉及國家安全或者極高機密的活動,若是隊員使用的口令出現錯誤,他們將付出沒法估計的代價。特別部隊的隊員都是層層篩選的精英人才,因此不但願發生這樣的事情。所以隊員必須牢記口令的設置方法。
口令S的內容知足:SN=X。顯然,S有可能也頗有多是一個無理數,因此限定S爲一個實數,它包含整數部分和小數部分,不包含小數點(即0.1將視做01)。口令的長度 M(10≤M≤50),將根據任務的難度而有所不一樣。
編程任務:給定X,N,M。計算口令的內容S。
輸入(rainbow .in):文件輸入,文件有且僅有一行包含3個整型數X,N,M,每一個數之間以一個空格符隔開。
輸出(rainbow.out):文件輸出,文件有且僅有一行,爲S的結果。
樣例輸入:2 2 10
樣例輸出:1414213652
注意:口令的最後一位請使用去尾法保留,不要使用四捨五入法保留。文件請不要包含多餘的空格和換行。
遞歸做爲一種強有力的數學和算法描述工具在Pascal語言中被普遍使用。一個函數、過程、概念或數學結構,若是在其定義或說明內部又直接或間接地出現有定義自己的引用(在過程或自定義函數中,又包含本身調用本身),則稱它們是遞歸的或者是遞歸定義的。
一個遞歸算法僅使用少許的程序編碼就可描述出解題所須要的屢次重複計算而不須要設計循環結構。使用遞歸求解問題,一般能夠將一個比較大的問題層層轉化爲一個與原問題相相似的規模較小的問題來求解,進而最終致使原問題的解決。
例如:n!的定義,咱們知道,在數學中n!通常定義爲:
1 若n=0
n!=
n×(n-1)! 若n>0
在n>0的公式中,又包含了(n-1)!,這就是遞歸定義。
利用遞歸方法,能夠用有限的語句來定義對象的無限集合。但在遞歸定義中,應該至少有一條是非遞歸的,即初始定義。如上面例子中的第一條,不然就變成了循環定義,產生邏輯性錯誤。
n!的遞歸定義的程序:
program find_n;
var n:integer;
t:longint;
procedure find(n:integer);
begin
if n=0 then t:=1
else
begin find(n-1);
t:=t*n end;
end;
begin
write('n=');
readln(n);
find(n);
writeln(n,'!=',t)
end.
遞歸調用(n進棧)達到結束條件時(n=0,t賦初值1)就中止調用開始返回,再把保存的值取出(n出棧),使n恢復原來的值,並計算t,返回主程序,輸出結果t。
3!遞歸是如何實現的?
(1)設n=3,開始調用過程find,稱爲第零層調用。
(2)因爲公式3!=32!,必須先求2!,即程序中的f(n-1),此時系統自動先保存n的原值3,再設n=2,進入第一層遞歸調用。
(3)由於2!=21!,因此係統又保存2,並設n=1,進入第2層調用。
(4)由於1!=10!,因此保存1,並設n=0,進入第3層調用。
(5)由於n=0,終止調用,t賦值1,返回4的入口點。
(6)取出執行4時保存的1,求出t=1t=1,返回3的入口點。
(7)取出執行3時保存的2,求出t=2t=2,返回2的入口點。
(8)取出執行2時保存的3,求出t=3t=6,返回1的入口點。
(9)返回主程序,輸出結果:t=6。
從上面分析的過程看到,因爲遞歸調用,需用同一變量名n,但值不一樣,因此在調用前必須先把n的原值保存,再賦以新值,而後進入調用。調用結束後,再把保存的值取出,使n恢復原來的值。在過程當中find中又包含find(n-1),即又調用了它本身,這就是遞歸調用。包含有遞歸調用的算法,就叫作遞歸算法。
遞歸調用會產生無終止運算的可能性,所以必須在適當時候終止遞歸調用,即在程序中必需要有終止條件。上面程序中,過程find的第一條語句就是終止條件。通常地,根據遞歸定義設計的遞歸算法中,非遞歸的初始定義,就用做程序中的終止 條件。
實踐證實,不是全部問題均可以用遞歸的方法處理,用遞歸算法編寫的程序也不必定是好程序。能夠看出,執行遞歸的過程既浪費時間又費存儲空間,所以有的語言系統,禁止使用遞歸,因爲計算機存儲容量的限制,編譯系統也不容許遞歸。但因遞歸特別符合人們的思惟習慣,遞歸算法的設計也要比非遞歸算法設計容易,因此當問題是遞歸定義,尤爲是當涉及的數據結構是遞歸定義的時候,使用遞歸算法特別合適。
應用遞歸算法可以求解的問題通常具備兩個特色:
①存在某個特定條件,在此條件下,可獲得指定的解,即遞歸在終止狀態。
②對任意給定的條件,有明確的定義規則,能夠產生新的狀態並將最終導出終止狀態,即存在致使問題求解的遞歸步驟。
遞歸是用棧來實現的。不過,遞歸恐怕不像想象得這麼簡單。首先,它體現了一個數學思想:化歸,即把一個問題轉化成另外一個的方法。遞歸比較特殊,它是轉化成性質類似,但規模更小的問題。
例3 閱讀程序NOIp2001_G。
program gao7_1;
function ack(m,n:integer):integer;
begin
if m=0 then ack:=n+1
else if n=0 then ack:=ack(m-1,1)else ack:=ack(m-1,ack(m,n-1))
end;
begin writeln(ack(3,4));
readln;
end.
分析:
這個程序咱們能夠用下面的函數表示。在解題時,通常都是用遞歸的方法去實現的,而遞歸方法將會計算五千多步,在競賽時這種方法是不可用的,而遞歸每每能夠用遞推去實現,所以,咱們在教學過程當中就講解了該函數的遞推過程,現將推導過程表示以下:
(1)咱們在遞歸過程當中發現該函數老是要調用到M=0,M=1及M=2的狀況,所以,咱們就考慮要推導ACK(3,N)必須首先推導ACK(0,N),ACK(1,N)以及ACK(2,N)的狀況。
(2)ACK(0,N)能夠由函數直接獲得,公式爲ACK(0,N)=N+1
(3)ACK(1,0)=ACK(0,1)=1+1=0+2
ACK(1,1)=ACK(0,ACK(1,0))=ACK(0,1+1)=1+1+1=1+2
ACK(1,2)=ACK(0,ACK(1,1))=ACK(0,1+2)=1+1+2=2+2
……
所以,咱們能夠聯想到ACK(1,N)=N+2。這個公式能夠用數學概括法證實之。(略)
根據上面的方法,咱們能夠方便地推導出下面一些公式:
(4)ACK(2,0)=ACK(1,1)=1+2=3(利用M=1的公式)
ACK(2,1)=ACK(1,ACK(2,0))=ACK(1,1+2)=3+2=5
(利用M=1的公式及ACK(2,0))
ACK(2,2)=ACK(1,ACK(2,1))=ACK(1,5)=5+2=(2+1)*2+1
……
所以,咱們能夠聯想到ACK(2,N)=(N+1)*2+1。一樣,這個公式能夠用數學概括法證實之。(略)
(5)ACK(3,0)=ACK(2,1)=(1+1)*2+1=5(利用M=2的公式)
ACK(3,1)=ACK(2,ACK(3,0))=ACK(2,5)=((1+1)*2+1+1)*2+1=2^3+2^2+1
ACK(3,2)=ACK(2,ACK(3,1))=ACK(2,13)=(2^3+2^2+1+1)*2+1=2^4+2^3+2^2+1=2^5-3
……
所以,咱們能夠聯想到ACK(3,N)=2^(N+3)-3。
例4 仍以例1爲例,找n個數的r個數的組合。
輸入:n,r =5,3
輸出:5 4 3
5 4 2
5 4 1
5 3 2
5 3 1
5 2 1
4 3 2
4 3 1
4 2 1
3 2 1
total=10 {組合的總數}
分析:所提示的10組數。首先固定第一位數(如5),其後是在另4個數中再「組合」2個數。這就將「5個數中3個數的組合」推到了「4個數中2個數的組合」上去了。第一位數能夠是n,r (如5,3),n個數中r個數組合遞推到n-1個數中r-1個數有組合,這是一個遞歸的算法。即:
var i:integer;
begin for i:=n downto r do
begin {固定i的輸出位置}
comb(i-1,r-1); {原過程遞推到i-1個數的r-1個數組合}
end;
end;
再考慮打印輸出格式。
[程序]
var k,n,r:integer;
Produrce comb(n,r:integer);
var i,temp:integer;
begin for i:=n downto r do
if (i<>n)and(k<>r) then {k爲過程外定義的}
begin for temp:=1 to (k-r)*3 do write(' '); {肯定i的輸出位置}
end;
write(i:3);
if i>1 then comb(i-1,r-1); {遞推到下一情形}
else writeln;
end;
begin {main}
write('n,r=');readln(n,r);
if r>n then
begin writeln('Input n,r error!');
halt;
end;
comb(n,r); {調用遞歸過程}
end;
【題目描述】
給定一個信封,最多隻容許粘貼N張郵票,計算在給定K(N+k≤40) 種郵票的狀況下(假定全部的郵票數量都足夠),如何設計郵票的面值,能獲得最大max ,使得1-max之間的每個郵資值都能獲得。
例如,N=3,K=2,若是面值分別爲1分、4分,則在l~6分之間的每個郵資值都能獲得(固然還有8分、9分和12分):若是面值分別爲1分、3分,則在1~7分之間的每個郵資值都能獲得。能夠驗證當N=3,K=2時,7分就是能夠獲得連續的郵資最大值,因此max=7,面值分別爲l分、3分。
【樣例輸入】
3 2 {輸入第一個數N,第二個數K,中間用空格間隔}
【樣例輸出】
1 3 {輸出的第一行面值分別爲l分、3分}
max=7 {輸出的第二連續的郵資最大值}
【題目描述】
一位教授邏輯學的教授有三名很是善於推理且精於心算的學生A,B和C。有一天,教授給他們3人出了一道題:教授在每一個人腦門上貼了一張紙條並告訴他們,每一個人的紙條上都寫了一個正整數,且某兩個數的和等於第三個。因而,每一個學生都能看見貼在另外兩個同窗頭上的整數,但卻看不見本身的數。
這時,教授先對學生A發問了:「你能猜出本身的數嗎?」A回答:「不能。」
教授又轉身問學生B:「你能猜出本身的數嗎?」B想了想,也回答:「不能。」
教授再問學生C一樣的問題,C思考了片刻後,搖了搖頭:「不能。」
接着,教授又從新問A一樣的問題,再問B和C,……,通過若干輪的提問以後,當教授再次詢問某人時,此人忽然露出了得意的笑容,把貼在本身頭上的那個數準確無誤地報了出來。
如今,若是告訴你:教授在第N次提問時,輪到回答問題的那我的猜出了貼在本身頭上的數是M,你能推斷出另外兩個學生的頭上貼的是什麼數嗎?
【提示】
在沒有人猜出本身頭上的數以前,你們對教授提問的回答始終都是「不能」;並且除此以外在A,B,C之間是沒有進行任何信息交流的。也就是說,每一個人推斷的依據僅僅是另外兩我的的頭上數,以及你們對教授的提問所作出的否認回答。
教授老是從學生A開始提問的。
你能夠假定,這3個聰明的學生可以根據已知的條件在最先的輪次猜出本身的數,而且永遠都不會猜錯。稍經分析和推理,你將得出如下結論:老是頭上貼着最大的那個數的人最早猜出本身頭上的數。
【輸入文件】
輸入文件爲guess.in。
該文件包括若干組測試數據,其中的每一行表明一組測試數據,由兩個整數N和M組成(即在教授第N次提問時,輪到回答問題的那我的猜出了貼在本身頭上的數是M)。兩個數之間用空格分隔開。最後,由-1 -1組成的一行標誌着輸入的數據結束。同時要求,0<N<500; 0<M<30000。
【輸出文件】
輸出文件爲guess.out。按照輸入文件中的順序依次給出各組數據的結果。
文件中對應每組數據輸出的第一行是一個整數p,是可能狀況的總數。接下來的p行,每一行包括3個數,分別爲貼在A、B、C頭上的3個數。輸出時,全部解按照A頭上的數增序排列;在A頭上的數相同的狀況下,按照B頭上的數增序排列。
【樣例輸入】
5 8
3 2
2 3
-1 -1
【樣例輸出】
3
2 8 6
5 8 3
6 8 2
1
1 1 2
1
2 3 1
回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇。這種走不通就退回再走的技術爲回溯法,而知足回溯條件的某個狀態的點稱爲「回溯點」。回溯算法是全部搜索算法中最爲基本的一種算法,其採用了一種「走不通就掉頭」思想做爲其控制結構
例5 再以例1說明,找n個數中r個數的組合。
分析:將天然數排列在數組A中。
A[1] A[2] A[3]
5 4 3
5 4 2
…
3 2 1
排數時從A[1]到A[2]再到A[3],後一個至少比前一個數小1,而且應知足ri+A[ri]>r。若ri+A[ri]≤r就要回溯,該關係就是回溯條件。爲直觀起見,當輸出一組組合數後,若最後一位爲1,也應做一次回溯(若不回,便由上述回溯條件處理)。
[程序]
program zuhe3;
type tp=array[1..100] of integer;
var n,r:integer;
procedure comb2(n,r:integer;a:tp);
var i,ri:integer;
begin ri:=1;a[1]:=n;
repeat
if ri<>r then {沒有搜索到底}
if ri+a[ri]>r then {判斷是否回溯}
begin
a[ri+1]:=a[ri]-1;
ri:=ri+1; {向前搜索}
end;
else
begin ri:=ri-1;
a[ri]:=a[ri]-1;
end; {回溯}
else
begin for j:=1 to r do write(a[j]:3);
writeln; {輸出組合數}
if a[r]=1 then {是否回溯}
begin ri:=ri-1;
a[ri]:=a[ri]-1;
end; {回溯}
else a[ri]:=a[ri]-1; {遞推到下一個數}
end;
until a[1]<>r-1;
end;
begin write('n,r=');
readln(n,r);
if r>n then begin writeln('Input n,r error!');
halt;
end;
comb2(n,r);
end.
【題目描述】
棋盤上A點有一個過河卒,須要走到目標B點。卒行走的規則:能夠向下、或者向右。同時,在棋盤上C點有一個對方的馬,該馬所在的點和全部跳躍一步可達的點稱爲對方馬的控制點。所以稱之爲「馬攔過河卒」。
棋盤用座標表示,A點(0, 0)、B點(n, m)(n, m爲不超過15的整數),一樣馬的位置座標是須要給出的。如今要求你計算出卒從A點可以到達B點的路徑的條數,假設馬的位置是固定不動的,並非卒走一步馬走一步。
【輸入】
一行四個數據,分別表示B點座標和馬的座標。
【輸出】
一個數據,表示全部的路徑條數。
【樣例】
knight.in knight.out
6 6 3 3 6
【題目描述】
有一個m×n格的迷宮(表示有m行、n列),其中有可走的也有不可走的,若是用1表示能夠走,0表示不能夠走,文件讀入這m×n個數據和起始點、結束點(起始點和結束點都是用兩個數據來描述的,分別表示這個點的行號和列號)。如今要你編程找出全部可行的道路,要求所走的路中沒有重複的點,走時只能是上下左右四個方向。若是一條路都不可行,則輸出相應信息(用-l表示無路)。
【輸入】
第一行是兩個數m,n(1<m,n<15),接下來是m行n列由1和0組成的數據,最後兩行是起始點和結束點。
【輸出】
全部可行的路徑,描述一個點時用(x,y)的形式,除開始點外,其餘的都要用「→」表示方向。若是沒有一條可行的路則輸出-1。
【樣例輸入】
5 6
1 0 0 1 0 1
1 1 1 1 1 1
0 0 1 1 1 0
1 1 1 1 1 0
1 1 1 0 1 1
1 1
5 6
【樣例輸出】
(1,2)→(2,1)→(2,2)→(2,3)→(2,4)→(2,5)→(3,5)→(3,4)→(3,3)→(4,3)→(4,4)→(4,5)→(5,5) →(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(2,5)→(3,5)→(3,4)→(4,4)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(2,5)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(3,4)→(3,3)→(4,3)→(4,4)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(3,4)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(3,4)→(4,4)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(3,4)→(2,4)→(2,5)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(3,4)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(3,4)→(4,4)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(4,3)→(4,4)→(3,4)→(2,4)→(2,5)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(4,3)→(4,4)→(3,4)→(3,5)→(4,5)→(5,5)→(5,6)
(1,1)→(2,1)→(2,2)→(2,3)→(3,3)→(4,3)→(4,4)→(4,5)→(5,5)→(5,6)
【題目描述】
排列與組合是經常使用的數學方法,其中組合就是從n個元素中抽出r個元素(不分順序且r≤n),咱們能夠簡單地將n個元素理解爲天然數1,2,…,n,從中任取r個數。現要求你不用遞歸的方法輸出全部組合。
例如:n=5,r=3,全部組合爲:
l 2 3;l 2 4;1 2 5;l 3 4;l 3 5;1 4 5;2 3 4;2 3 5;2 4 5;3 4 5。
【輸入】
一行兩個天然數n、r(1<n<21,1≤r≤n)。
【輸出】
全部的組合,每個組合佔一行且其中的元素按由小到大的順序排列,每一個元素佔3個字符的位置,全部的組合也按字典順序。
【樣例輸入】
5 3
【樣例輸出】
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
遞推是迭代算法中一種用若干步可重複的簡單運算來描述複雜數學問題的方法,以便於計算機進行處理。它與遞推關係的思想徹底一致,由邊界條件開始日後逐個推算,在通常狀況下,效率較高,編程也很是的方便。可是,咱們通常只須要求遞推關係的第n項,而邊界條件與第n項前面之間的若干項的信息是咱們不須要的,若是採用遞推的方法來求解的話,第n項以前的每一項都必須計算出來,最後才能獲得所須要的第n項的值。這是遞推沒法避免的,從而在必定程度上影響了程序的效率。固然在大多數狀況下,採用遞推的方法仍是可行的,在競賽中,使用遞推的方法編程,一般會在時限內出解。固然,更好的求解方法還有母函數法、迭代概括法等。
例1 青蛙過河(frog.pas)。
【題目描述】
有一條河,左邊一個石墩(A區)上有編號爲1,2,3,4,…,n的n只青蛙,河中有k個荷葉(C區),還有h個石墩(D區),右邊有一個石墩(B區),如圖3-1所示。n只青蛙要過河(從左岸石墩A到右岸石墩B),規則爲:
圖3-1 青蛙過河示意圖
(1)石墩上能夠承受任意多隻青蛙,荷葉只能承受一隻青蛙(不論大小)。
(2)青蛙能夠:A→B(表示能夠從A跳到B,下同),A→C,A→D,C→B,D→B,D→C,C→D。
(3)當一個石墩上有多隻青蛙時,則上面的青蛙只能跳到比它大1號的青蛙上面。
你的任務是對於給出的h、k,計算並輸出最多能有多少隻青蛙能夠根據以上規則順利 過河?
【樣例輸入】
2 3 {河中間有2個石礅,3個荷葉}
【樣例輸出】
16 {最多有16只青蛙能夠按照規則過河}
【算法分析】
從具體到通常,推導過程以下:
f(0,0)=1;
f(0,k)=k+1; {如k=3時,有4只青蛙能夠過河}
f(1,k)=2(k+1); ` {遞推思想}
……
以此類推:f(2,k)=(2×(k+1))×2=22(k+1);
……
結論爲:f(h,k)=2h(k+1)
[程序]
program frog(input,output);
var h,k,i,s:integer;
begin
assign(input,'frog.in');
assign(output,'frog.out');
reset(input);
rewrite(output);
readln(h,k);
s:=k+1;
for i:=1 to h do s:=s*2;
writeln(s);
close(input);close(output)
end.
例2 排序集合(sort.pas)。
【題目描述】
對於集合N={1,2,…,n}的子集,定義一個稱之爲「小於」的關係:設S1={X1,X2,…,Xi},(X1<X2<…<Xi),S2={Y1, Y2, …,Yj},(Y1<Y2<…<Yj),若是存在一個k,(0≤k≤min(i,j)),使得X1=Y1,…,Xk=Yk,且k=i或X(k+1) <Y(k+1),則稱S1「小於」S2。
你的任務是,對於任意的n(n≤31)及k(k<2n),求出第k小的子集。
【輸入】
輸入文件僅一行,包含兩個用空格隔開的天然數,n和k。
【輸出】
輸出文件僅一行,使該子集的元素,由小到大排列。空集輸出0。
【樣例輸入】
3 4
【樣例輸出】
1 2 3
【算法分析】
咱們知道,n個元素構成的集合有2n種狀況。本題的意思是:把這些集合按照某種規則排序,而後輸入k,輸出第k個集合。因此排序的規則是本題的關鍵,其實很簡單,當n=3時,8個集合排序以下:{ }<{1}<{l,2}<{l,2,3}<{1,3}<{2}<{2,3}<{3},你發現規律了嗎?具體算法爲:先推出第k小的一個子集的第一個數宇是多少,第一個數字肯定了以後,再推出第二個數字,從第一個數字加1一直計算累加集合個數,直到獲得不超過k的最大的那個數字,就是第二位數字,這樣一直遞推,推到最後一個。注意:終止條件是有了n個數字或者第i個數字爲空,這時遞推終止,輸出最後的結果。
[程序]
program sort(input,output);
type arr=array[0..31] of longint;
var a:arr;
n,i,j,k:longint;
begin
assign(input,'sort.in');
assign(output,'sort.out');
reset(input);
rewrite(output);
readln(n,k);
fillchar(a,sizeof(a),0);
a[n]:=1; a[0]:=1;
for i:=n-1 downto 1 do {a[i]=2i-n}
a[i]:=a[i+1]*2;
i:=0; j:=1;
while k>1 do {如下爲一位一位推出數字}
begin
while (i<=n) and (k>a[i]) do
begin
dec(k,a[i]);
inc(i)
end;
if j<>1 then write(' ');
inc(j);
write(i);
a[i]:=1
end;
if i=0 then writeln(0);{判空集}
close(input);close(output)
end.
例3 諸侯安置(empire.pas)。
【題目描述】
好久之前,有一個強大的帝國,它的國土成正方形狀,如圖3-2所示。
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
圖3-2 諸侯安置示意圖(原圖)
這個國家有若干諸侯。因爲這些諸侯都曾立下赫赫戰功,國王準備給他們每人一塊封地(正方形中的一格)。可是,這些諸侯又很是好戰,當兩個諸侯位於同一行或同一列時,他們就會開戰。如圖3-3爲n=3時的國土,陰影部分表示諸侯所處的位置。前兩幅圖中的諸侯能夠互相攻擊,第三幅則不能夠。
(1) (2) (3)
圖3-3 諸侯安置示意圖
導致國家動盪不安國王也不肯意看到他的諸侯們互相開戰,所以,他但願經過合理地安排諸侯所處的位置,使他們兩兩之間都不能攻擊。
如今,給出正方形的邊長n,以及須要封地的諸侯數量k,要求你求出全部可能的安置方案數。(n≤l00,k≤2n2-2n+1)。因爲方案數可能不少,你只須要輸出方案數除以504的餘數便可。
【輸入】
僅一行,兩個整數n和k,中間用一空格隔開。
【輸出】
一個整數,表示方案數除以504的餘數。
【樣例輸入】
2 2
【樣例輸出】
4
【樣例說明】
四種安置方案如圖3-4所示。
注意:鏡面和旋轉的狀況屬於不一樣的方案。
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(1) (2) (3) (4)
圖3-4 安置方案
【算法分析】
從新描述一下問題,其實就是在一個邊長爲2n-1的正菱形(如圖3-2爲n=3的情形)上擺放k個棋子,使得任意兩個棋子都不在同一行、同一列。試問:這樣的擺法共有多少種?
看到這道題目,咱們就會當即想起一道經典的老題目:n皇后問題。這道題目與n皇后問題很是類似。但有兩個不一樣點:一是n皇后問題能夠斜着攻擊對方棋子,而本題不能;二是n皇后問題是在n,n的正方形棋盤上面放置k個皇后,而本題倒是在一個正菱形上擺放。咱們試着先將n皇后變爲不可斜攻的,再做思考,若是不可以斜着攻擊,n皇后的公式是:(C(k,n))2×k!。可是本題不是一個正方形,因此這個公式對本題好像沒有任何幫助。看來只可以從另一個角度思考問題了。
首先,想到在2n-1列中任意取出k列進行具體分析,這樣一來問題就轉化成:有一個長爲k的數列(無重複元素),每個數在一個不定的區間[a,b]當中,第i個數必定在區間[ai,bi]之間,求這樣的數列有多少種?若是就是這個問題,那麼比較難解決,但若把這個數列放在本題中,就比較簡單。由於題目中任意兩個區間都是一種包含關係。能夠先把區間按照長度排一下序,就能夠看出來,再用乘法原理進行求解便可。可是,n最多可到100,k最多可到50,窮舉要進行C(50,100)種方案!顯然沒法在規定的時間內出解!那麼怎麼辦呢?再繼續分析一下問題發現,裏面有重疊子問題。若是一個列做爲最後一列,且這一列以及前面全部列共放置p個諸侯,設有q種狀況,那麼這一列後面的全部列共放置p+1個棋子的方案數都要用到q,從而引用乘法原理。並且在窮舉過程當中,這一個工做作了許多遍,因此乾脆用遞推。遞推前,先把圖形轉化爲相似圖3-5的形式(即列排序)。
設f[i,j]表示以第i列爲最後一列,放置j個棋子的總方案數,得出公式:
注意:當k≥2n-1時,問題無解。
[程序]
var i,j,k,n,l,s:longint;
f:array[1..200,1..200] of longint;
function make(p:longint):longint;
begin
if odd(p) then make:=p else make:=p-1;
end;
begin
assign(input,'empire.in');reset(input);
assign(output,'empire.out');rewrite(output);
readln(n,k);
if k=0 then begin writeln(1);close(output);halt
end;
if k>=2×n-1 then begin writeln(0);
close(output);halt;
end;
for i:=1 to 2×n-1 do
if odd(i) then f[i,1]:=i else f[i,1]:=i-1;
for i:=1 to 2×n-1 do
for j:=2 to i do
for l:=1 to i-j+1 do
f[i,j]:=(f[i,j]+f[i-l,j-1] ×(make(i)-j+1)) mod 504;
i:=2×n-1;
if k=1 then begin writeln((i×(i+1) div 2-i div 2) mod 504);
close(output);halt end else
for i:=k to 2×n-1 do inc(s,f[i,k]);
writeln(s mod 504);
close(input);
close(output);
end.
【題目描述】
一個旅行家想駕駛汽車以最少的費用從一個城市到另外一個城市(假設出發時油箱是空的)。給定兩個城市之間的距離D1、汽車油箱的容量C(以升爲單位)。每升汽油能行駛的距離D2、出發點每升汽油價格P和沿途油站數N(N能夠爲零),油站i離出發點的距離Di、每升汽油價格Pi(i=l,2,…N)(計算結果四捨五入至小數點後兩位)。若是沒法到達目的地,則輸出「no solution」。
【輸入】
第一行,第一個數兩個城市之間的距離D2,第二個數汽車油箱的容量C,第三個數每升汽油能行駛的距離D2,第四個數出發點每升汽油價格P,第五個數沿途油站數N。
從第二行開始,每行有兩個數,第一個數爲油站i離出發點的距離Di,第二個數爲每升汽油價格Pi,中間用空格間隔。
【樣例輸入】
275.6 11.9 27.4 2.8 2
102.0 2.9
220.0 2.2
【樣例輸出】
26.95(該數據表示最小費用)
【算法分析】
看到題目後,很容易想到遞推。事實上,要用的油是肯定的(D1/D2),價錢最便宜的油的站Q的油顯然應該多買,到達Q這個油站時汽車剩油不爲0的方案必定不是最優的。這是由於,若是剩下P升油,顯然不如當初少買P升,改在Q這裏買P升划算!(Q最便宜嘛!)
每次都僞裝裝滿油,用的時候先用便宜的,由於把貴的留在後面「反悔」!這樣計算費用時只考慮真正使用的。能夠用優先隊列(用堆來實現)來進行替換和使用油。也能夠模擬但效率不高。
模擬題。按常規思想:逐步地模擬,直至又回到初始狀態時爲止。但這樣所有模擬基本上沒法在規定的時間內出解,必須作一些改進。模擬題並不須要太多的算法知識,主要考察選手的數據結構的應用以及編程能力。
例1 津津的儲蓄計劃(save.pas)。
【題目描述】
津津的零花錢一直都是本身管理。每月的月初媽媽給津津300元錢,津津會預算這個月的花銷,而且總能作到實際花銷和預算的相同。
爲了讓津津學習如何儲蓄,媽媽提出,津津能夠隨時把整百的錢存在她那裏,到了年底她會加上20%利息、連本帶息還給津津。所以,津津制定了一個儲蓄計劃:每月的月初,在獲得媽媽給的零花錢後,若是她預計到這個月的月末手中還會有多於100元或剛好100元,她就會把整百的錢存在媽媽那裏,剩餘的錢留在本身手中。
例如,11月初津津手中還有83元,媽媽給了津津300元。津津預計11月的花銷是180元,那麼她就會在媽媽那裏存200元,本身留下183元。到了11月末,津津手中會剩下3元錢。
津津發現這個儲蓄計劃的主要風險是,存在媽媽那裏的錢在年底以前不能取出。有可能在某個月的月初,津津手中的錢加上這個月媽媽給的錢,不夠這個月的原定預算。若是出現這種狀況,津津將不得不在這個月省吃儉用,壓縮預算。
如今請你根據2004年1月到12月每月津津的預算,判斷會不會出現這種狀況。若是不會,計算到2004年年底,媽媽將津津日常存在她那裏的錢加上20%的利息,一併還給津津以後,津津手中會有多少錢。
【輸入】
輸入文件save.in包括12行數據,每行包含一個小於350的非負整數,分別表示1月到12月津津的預算。
【輸出】
輸出文件save.out包括一行,這一行只包含一個整數。若是儲蓄計劃實施過程當中出現某個月錢不夠用的狀況,輸出-X,X表示出現這種狀況的第一個月;不然輸出到2004年年底津津手中會有多少錢。
【樣例輸入1】
290
230
280
200
300
170
340
50
90
80
200
60
【樣例輸出1】
-7
【樣例輸入2】
290
230
280
200
300
170
330
50
90
80
200
60
【樣例輸出2】
1580
【算法分析】
最簡單、最基本的模擬加判斷,連數組都不用開。只要讀清題目,而後動手作就能夠了。解決此類問題沒有什麼技巧,最重要的是不在關鍵時刻出現低級錯誤。
[程序]
program save ;
var f1,f2:text;
a:array[1..12] of integer;
i,tol,s:longint;
begin
assign(f1,'save.in');
reset(f1);
assign(f2,'save.out');
rewrite(f2);
tol:=0;s:=0;
for i:=1 to 12 do
begin
readln(f1,a[i]);
tol:=tol+300-a[i];
if tol<0 then
begin
writeln(f2,'-',i);
close(f2);
halt;
end;
s:=s+100*(tol div 100);
tol:=tol mod 100;
end;
writeln(f2,tol+s+s div 5);
close(f2);
end.
例2 小球鍾(ball.pas)。
【問題描述】
時間是運動的一種方式。咱們經常用運動來度量時間。例如,小球鍾是一個經過不斷在軌道上移動小球來度量時間的簡單設備。每分鐘,一個轉動臂將一個小球從小球隊列的底部擠走,並將它上升到鐘的頂部並將它安置在一個表示分鐘,5分鐘,15分鐘和小時的軌道上。這裏能夠顯示從1:00~24:59(這正是奇怪之處)範圍內的時間,如有3個球在分鐘軌道,1個球在5分鐘軌道,2個球在15分鐘軌道及15個球在小時軌道上,就顯示時間15:38。
當小球經過鐘的機械裝置被移動後,它們就會改變其初始次序。仔細研究它們次序的改變,能夠發現相同的次序會不斷出現。因爲小球的初始次序最後早晚會被重複,因此這段時間的長短是能夠被度量的,這徹底取決於所提供的小球的總數。
小球鐘的運做:每分鐘,最近最少被使用的那個小球從位於球鍾底部的小球隊列被移走,並將上升並安置於顯示分鐘的軌道上,這裏能夠放置4個小球。當第5個小球滾入該軌道,它們的重量使得軌道傾斜,原先在軌道上的4個小球按照與它們原先滾入軌道的次序相反的次序加入到鍾底部的小球隊列。引發傾斜的第5個小球滾入顯示5分鐘的軌道。該軌道能夠放置2個球。當第3個小球滾入該軌道,它們的重量使得軌道傾斜,原先2個小球一樣以相反的次序加入鍾底部的小球隊列。而這第3個小球滾入了顯示15分鐘的軌道。這裏能夠放置3個小球。當第4個小球滾入該軌道,它們的重量使得軌道傾斜,原先在軌道上的3個小球按照與它們原先滾入軌道的次序相反的次序加入到鍾底部的小球隊列,而這第4個小球滾入了顯示小時的軌道。該軌道一樣能夠放置23個球,但這裏有一個外加的固定的不能被移動的小球,這樣小時的值域就變爲1~24。從5分鐘軌道滾入的第24個小球將使小時軌道傾斜,這23個球一樣以相反的次序加入鍾底部的小球隊列,而後那第24個小球一樣加入鍾底部的小球隊列。
【輸入】
輸入定義了一序列的小球時鐘。每一個時鐘都按照前面描述的那樣運做。全部時鐘的區別僅在於它們在1:00時鐘啓動時刻小球初始個數的不一樣。在輸入的每行上給出一個時鐘的小球數,它並不包括那個在小時軌道上的固定的小球。合法的數據應在33~250之間。0代表輸入的結束。
【輸出】
輸出中每一行只有一個數,表示對應的輸入情形中給出的小球數量的時鐘在通過多少天的運行能夠回到它的初始小球序列。
【樣例輸入】
33
55
0
【樣例輸出】
22
50
【算法分析】
該題是典型的模擬題。按常規思想:逐分鐘地模擬小球鐘的運做,直至鍾底部的小球隊列重又回到初始狀態時爲止。這期間流逝的天數即爲小球鐘的運做週期。但這樣所有模擬基本上沒法在規定的時間內出解,必須作一些改進。
因而,咱們想到經過模擬出每一個小球回到原來位置上所需的天數,而後求它們的最小公倍數。可是,若是還是單純的模擬,速度仍然很慢。咱們能夠先模擬小球鍾最早24小時的運行狀況,獲得一天後的鐘底部的新小球隊列。有了這個條件後,咱們能夠在兩次的鐘底部小球隊列間創建起一種置換。設初始時,鍾底部的小球編號依次是:1,2,3,…n。一天後,鍾底部的小球編號依次是:p1,p2,p3,…pn。則咱們能夠創建這樣的置換:
1 2 3 … n
p1 p2 p3 … pn
注意到小球鐘的運做規則保證了上述置換是不變的,就能夠計算出小球鍾運行48小時後,72小時後……,鍾底部的小球隊列狀況,直至隊列狀況從新是1,2,3,…,n。這樣,在求得以上置換的基礎上,咱們能夠求每個小球回到原位置的週期,而後求它們的最小公倍數便可。
[程序]
program ball
var n, i : integer; m1, m2, m3, m4, m : string; c, c2 : char; now, long : longint;
function gcd(a, b : longint) : longint;
begin
if b = 0 then gcd := a
else if b = 1 then gcd := 1
else gcd := gcd(b, a mod b);
end;
function reverse(s : string) : string;
var s2 : string;
begin
s2 := '';
while s <> '' do
begin
s2 := s[1] + s2;
delete(s, 1, 1);
end;
reverse := s2;
end;
begin
assign(input, 'ball.in'); reset(input);
assign(output, 'ball.out'); rewrite(output);
readln(n);
while n > 0 do
begin
m := '';
for i := 1 to n do m := m + chr(i);
repeat
c := m[1];
delete(m, 1, 1);
if length(m1) < 4 then m1 := m1 + c
else
begin
m := m + reverse(m1);
m1 := '';
if length(m2) < 2 then m2 := m2 + c
else
begin
m := m + reverse(m2);
m2 := '';
if length(m3) < 3 then m3 := m3 + c
else
begin
m := m + reverse(m3);
m3 := '';
if length(m4) < 23 then m4 := m4 + c
else
begin
m := m + reverse(m4) + c;
m4 := '';
end;
end;
end;
end;
until (m1 ='') and (m2 = '') and (m3 = '') and (m4 = '');
now := 1;
for i := 1 to length(m) do
if m[i] <> #0 then
begin
c := m[i];
m[i] := #0;
long := 1;
while m[ord(c)] <> #0 do
begin
inc(long);
c2 := m[ord(c)];
m[ord(c)] := #0;
c := c2;
end;
now := (now * long) div gcd(now, long);
end;
writeln(now);
readln(n);
end;
close(output);
close(input);
end.
例3 奶牛(eat.pas)。
【題目描述】
一個農夫有n(n≤1000)頭奶牛,可因爲產奶太少,他決定把當天產奶最少的牛殺掉,但他有點捨不得,若是當天不僅一頭奶牛產奶,至少他便放過它們。這些奶牛產奶是週期性的,他已開始就想知道有多少奶牛可倖免遇難(可能會被全殺),每頭奶牛週期不會超過10(每頭奶牛產奶量≤250)。
【輸入】
注:T——數據總數
N——奶牛總數
第一頭奶牛週期天數,天天產奶數;
第二頭奶牛週期天數,天天產奶數;
……
第n頭奶牛週期天數,天天產奶數。
【樣例輸入】
1
4
4 7 1 2 9
1 2
2 7 1
1 2
【樣例輸出】
2 6
注:2——最後剩下2頭奶牛
6——最後一頭牛是在第六天被殺的
【算法分析】
直述型模擬,每頭奶牛產奶數 P:= p mod n +1;找最小值,最小值奶牛;用哈希表判奶牛是否死亡;注意每一個數據的初始化。
[程序]
program eat
var h,p,pro:array [1..1000+10] of longint;
m:array [1..1000+10,1..250] of longint;
del:array [1..1000+10] of boolean;
k,i,j,round,n:longint;
ob,min,ans,ans2,now,change:longint;
allk:boolean;
begin
assign(input,'eat.in');reset(input);assign(output,'eat.out'); rewrite(output);
readln(k);
for round:= 1 to k do
begin
fillchar(h,sizeof(h),0);
fillchar(m,sizeof(m),0);
fillchar(del,sizeof(del),0);
fillchar(pro,sizeof(pro),0);
ans:=0; ans2:=0; change:=0;
readln(n);
for i:= 1 to n do
begin
read(p[i]);
for j:= 1 to p[i] do read(m[i,j]);
readln;
end;
fillchar(h,sizeof(h),0);
while true do
begin
inc(ans);
for i:= 1 to n do if not del[i] then
begin
h[i]:=h[i] mod p[i] +1;
pro[i]:=m[i,h[i]];
end;
min:=maxlongint;
for i:= 1 to n do if not del[i] then
if pro[i]<min then min:=pro[i];
now:=0;
for i:= 1 to n do if not del[i] then
if pro[i]=min then begin inc(now); ob:=i end;
if now=1 then begin del[ob]:=true; ans2:=ans; change:=0;
end
else inc(change);
if change>500 then break;
allk:=true; for i:= 1 to n do if not del[i] then allk:=false;
if allk then break;
end;
ans:=0;
for i:= 1 to n do if not del[i] then inc(ans);
writeln(ans,' ',ans2);
end;
close(input); close(output);
end.
例4 貓和老鼠(catmou.pas)。
【題目描述】
貓和老鼠在10×10的方格中運動(如圖3-6),例如:
*...*.....
......*...
...*...*..
..........
...*.C....
*.....*...
...*......
..M......*
...*.*....
.*.*......
圖3-6 貓和老鼠在10×10方格中的運動圖
C=貓(CAT)
M=老鼠(MOUSE)
*=障礙物
.=空地
貓和老鼠每秒中走一格,若是在某一秒末它們在同一格中,咱們稱它們「相遇」。
注意:「對穿」是不算相遇的。貓和老鼠的移動方式相同:平時沿直線走,下一步若是會走到障礙物上去或者出界,就用1秒的時間作一個右轉90°。一開始它們都面向北方。
編程計算多少秒之後他們相遇。
【輸入】10行,格式如圖3-6所示。
【輸出】相遇時間T。若是無解,輸出-1。
【樣例輸入】
*...*.....
......*...
...*...*..
..........
...*.C....
*.....*...
...*......
..M......*
...*.*....
.*.*......
【樣例輸出】
49
【算法分析】
題目沒什麼好的辦法,只有模擬貓和老鼠。
[程序]
program catmon
var
ipt,opt:text;
a:array[0..11,0..11] of char;
i,b,cl,cw,ml,mw,aim,bim:byte;
k:longint;
begin
assign(ipt,'catmou.in'); assign(opt,'catmou.out');
reset(ipt);
rewrite(opt);
for i:=1 to 10 do
begin
for b:=1 to 9 do
read(ipt,a[b,i]);
readln(ipt,a[10,i]);
end;
for i:=0 to 11 do
begin
a[i,0]:='*';
a[i,11]:='*';
a[0,i]:='*';
a[11,i]:='*';
end;
for i:=1 to 10 do
for b:=1 to 10 do
begin
if a[i,b]='C' then
begin
cw:=b;
cl:=i;
end;
if a[i,b]='M' then
begin
mw:=b;
ml:=i;
end;
end;
aim:=1; bim:=1; k:=0;
repeat
if k>99999 then begin k:=-1; break; end;
inc(k);
if aim=1 then
begin
if a[ml,mw-1]='*' then inc(aim);
if a[ml,mw-1]<>'*' then mw:=mw-1;
end
else
begin
if aim=2 then
begin
if a[ml+1,mw]='*' then inc(aim);
if a[ml+1,mw]<>'*' then ml:=ml+1;
end
else
begin
if aim=3 then
begin
if a[ml,mw+1]='*' then inc(aim);
if a[ml,mw+1]<>'*' then mw:=mw+1;
end
else
begin
if aim=4 then
begin
if a[ml-1,mw]='*' then aim:=1;
if a[ml-1,mw]<>'*' then ml:=ml-1;
end;
end;
end;
end;
if bim=1 then begin
if a[cl,cw-1]='*' then inc(bim);
if a[cl,cw-1]<>'*' then cw:=cw-1;
end
else
begin
if bim=2 then begin
if a[cl+1,cw]='*' then inc(bim);
if a[cl+1,cw]<>'*' then cl:=cl+1;
end
else
begin
if bim=3 then begin
if a[cl,cw+1]='*' then inc(bim);
if a[cl,cw+1]<>'*' then cw:=cw+1;
end
else
begin
if bim=4 then begin
if a[cl-1,cw]='*' then bim:=1;
if a[cl-1,cw]<>'*' then cl:=cl-1;
end;
end;
end;
end;
until (cl=ml) and (cw=mw);
write(opt,k);
close(ipt); close(opt);
end.
例5 字母運算(cowcul.pas)。
【題目描述】
有4個字母:U、C、D、V,它們能夠相加,也能產生進位,如圖3-7所示:V+V=V,U+D=V,但他要進U位現有2個由上述字母構成的「數」(五位),只對第2個數進行操做,有3種操做。
圖3-7 字母運算示意圖
A:把第2個數變成兩數之和;
L:在數後加一個V(對UVUV進行操做後爲UVUVV,至關於字符串相加);
R:在數前加一個V(同上),去掉末位。
現給你兩個數以及3個操做,再給你一個數(8位),把兩數最前面連續的V去掉(VVVUV變成UV),判斷是否相等,相等輸出YES,不等則輸出NO。
【樣例輸入】
5
VVVVU
VVVVU
A
A
A
VVVVVVUV
VVCCV
VVDCC
L
R
A
VVVVUCVC
VVCCV
VVDCC
R
L
A
VVVVUCVV
VVUUU
VVVVU
A
N
N
VVVVVUCU
DDDDD
VVVVU
A
L
L
UVVVVVVV
【樣例輸出】
COWCULATIONS OUTPUT
YES
YES
YES
NO
YES
END OF OUTPUT
【算法分析】
此題爲模擬題。作法:題目轉換。題目中的U、V、C、D,其實都是忽悠你的,說白了就是對應了四進制的加法、進位、讓位等操做;這樣題目就轉換成了讀入兩個四進制數而後按操做與第三個數對比大小便可。時間:O(1)。
[程序]
program cowcul;
type num=array [0..100] of longint;
var a,b,x: num;
tests,round,i,j: longint;
c: char;
function magic(c:char):integer;
begin
case c of
'V': exit(0);
'U': exit(1);
'C': exit(2);
'D': exit(3);
end;
exit(0);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure dd(var a:num; n:longint);
var l,r,t:longint;
begin
l:=1; r:=n;
while l<r do
begin
t:=a[l]; a[l]:=a[r]; a[r]:=t;
inc(l); dec(r);
end;
while a[n]=0 do dec(n);
a[0]:=n;
end;
procedure sum(a:num; var b:num);
var l,r,t,i:longint;
begin
l:=max(a[0],b[0]);
for i:= 1 to l do
begin
inc(b[i+1],(a[i]+b[i]) div 4);
b[i]:=(a[i]+b[i]) mod 4;
end;
if b[l+1]<>0 then b[0]:=l+1 else b[0]:=l;
end;
procedure add(var b:num);
var i:longint;
begin
for i:= b[0] downto 1 do b[i+1]:=b[i];
b[1]:=0;
inc(b[0]);
end;
procedure dda(var b:num);
var i:longint;
begin
for i:= 2 to b[0] do b[i-1]:=b[i];
b[b[0]]:=0; dec(b[0]);
end;
function Compare(a,b:num):boolean;
var l:longint;
begin
l:=max(b[0],a[0]);
for i:= 1 to l do
if b[i]<>a[i] then exit(false);
exit(true);
end;
begin
assign(input,'cowcul.in'); reset(input);
assign(output,'cowcul.out'); rewrite(output);
writeln('COWCULATIONS OUTPUT');
readln(tests);
for round:= 1 to tests do
begin
fillchar(a,sizeof(a),0);
fillchar(b,sizeof(b),0);
fillchar(x,sizeof(x),0);
for i:= 1 to 5 do begin read(c); a[i]:=magic(c) end; readln; dd(a,5);
for i:= 1 to 5 do begin read(c); b[i]:=magic(c) end; readln; dd(b,5);
for i:= 1 to 3 do
begin
readln(c);
case c of
'A': sum(a,b);
'L': add(b);
'R': dda(b);
end;
end;
for i:= 1 to 8 do begin read(c); x[i]:=magic(c) end; readln; dd(x,8);
if compare(b,x) then writeln('YES') else writeln('NO');
end;
writeln('END OF OUTPUT');
close(input); close(output);
end.
例6 內存分配(memory.pas)。
【題目描述】
內存是計算機重要的資源之一,程序運行的過程當中必須對內存進行分配。經典的內存分配過程是這樣進行的:
(1)內存之內存單元爲基本單位,每一個內存單元用一個固定的整數做爲標識,稱爲地址。地址從0開始連續排列,地址相鄰的內存單元被認爲是邏輯上連續的。咱們把從地址i開始的s個連續的內存單元稱爲首地址爲i長度爲s的地址片。
(2)運行過程當中有若干進程須要佔用內存,對於每一個進程有一個申請時刻T,須要內存單元數M及運行時間P。在運行時間P內(即T時刻開始,T+P時刻結束),這M個被佔用的內存單元不能再被其餘進程使用。
(3)假設在T時刻有一個進程申請M個單元,且運行時間爲P,則:
①若T時刻內存中存在長度爲M的空閒地址片,則系統將這M個空閒單元分配給該進程。若存在多個長度爲M個空閒地址片,則系統將首地址最小的那個空閒地址片分配給該進程。
②若是T時刻不存在長度爲M的空閒地址片,則該進程被放入一個等待隊列。對於處於等待隊列隊頭的進程,只要在任一時刻,存在長度爲M的空閒地址片,系統立刻將該進程取出隊列,併爲它分配內存單元。
注意:在進行內存分配處理過程當中,處於等待隊列隊頭的進程的處理優先級最高,隊列中的其餘進程不能先於隊頭進程被處理。
如今給出一系列描述進程的數據,請編寫一程序模擬系統分配內存的過程。
【輸入】
第一行是一個數N,表示總內存單元數(即地址範圍從0~N-1)。從第二行開始每行描述一個進程的3個整數T、M、P(M≤N)。最後一行用3個0表示結束。數據已按T從小到大排序。輸入文件最多10000行,且全部數據都小於109。輸入文件中同一行相鄰兩項之間用一個或多個空格隔開。
【輸出】
每組數據輸出2行。第一行是所有進程都運行完畢的時刻。第二行是被放入過等待隊列的進程總數。
【樣例輸入】
10
1 3 10
2 4 3
3 4 4
4 1 4
5 3 4
0 0 0
【樣例輸出】
12
2
【算法分析】
本題雖然數據規模比較大,但輸入是比較隨機的,也就是說,單純的模擬就能夠了。
具體的方法是,用一個堆存儲每一個進程的結束時間,以便及時釋放內存;同時用一個雙向鏈表按每一個進程在內存中的順序存儲其地址,這樣在有新進程申請時就能夠經過遍歷鏈表而找到合適的地址運行它(所說的「輸入比較隨機」,就是指輸入沒有故意使得每次插入進程時都要遍歷整個鏈表)。固然,還要有一個等待隊列。爲了讓堆和鏈表同時更新,須要在堆和鏈表的對應元素間創建互鏈。這樣處理每一個進程的流程就能夠描述爲:①讀入一個進程的信息;②將在新進程開始前或開始時結束的進程刪除,檢查等待隊列首的進程是否能夠運行;③判斷新進程是能夠運行仍是需放進等待隊列。爲了在全部進程都放進堆後能夠清空堆,能夠在最後假設讀入了一個在無窮大時間結束的進程。
上述流程中的第②步的實現要注意:第一種作法,不能先把在新進程開始前或開始時結束的進程通通刪除,再檢查等待隊列;第二種作法,也不能刪除一個進程就檢查一次隊列。正確的作法是:把與堆頂進程同時結束的進程所有刪除,檢查等待隊列,重複進行直至堆頂進程的結束時間晚於新進程的開始時間。爲何不能採用第二種作法呢?由於堆中元素僅僅按結束時間排序,若刪除一個就檢查一次等待隊列,則可能形成在同時結束的進程中,地址大的先被刪除,等待隊列首的進程就正好被放在大地址了,而實際上它應該放在小地址,這樣就形成了之後的混亂。
[程序]
program memory;
const
maxprogs=10001;
var
heap:array[0..maxprogs]of record fin:longint;pchain:word;end;
chain:array[0..maxprogs]of record left,right:longint; pheap,pre, ext:word; end;
wait:array[1..maxprogs]of record mem,time:longint;end;
h,n,a,b,c,f,r,now,p:longint;
function where(mem:longint):word;
begin
where:=0;
while (chain[where].next>0) and
(chain[chain[where].next].left-chain[where].right<mem) do
where:=chain[where].next;
end;
procedure ins(where,fintime,mem:longint);
var
p,q:word;
begin
inc(n);
with chain[n] do begin
left:=chain[where].right;right:=left+mem;
pre:=where;next:=chain[where].next;
end;
chain[where].next:=n;chain[chain[n].next].pre:=n;
inc(h);p:=h;q:=p shr 1;
while (p>1) and (fintime<heap[q].fin) do begin
heap[p]:=heap[q];
chain[heap[p].pchain].pheap:=p;
p:=q;q:=p shr 1;
end;
with heap[p] do begin fin:=fintime;pchain:=n;end;
chain[n].pheap:=p;
end;
function pop:longint;
var
p,l,r:word;
begin
pop:=heap[1].fin;
p:=heap[1].pchain;chain[chain[p].pre].next:=chain[p].next;chain[chain[p].next].pre:=chain[p].pre;
p:=1;l:=2;r:=3;
repeat
if (r<h) and (heap[h].fin>heap[r].fin) and (heap[r].fin<heap[l].fin) then begin
heap[p]:=heap[r];
chain[heap[p].pchain].pheap:=p;
p:=r;
end
else if (l<h) and (heap[h].fin>heap[l].fin) then begin
heap[p]:=heap[l];
chain[heap[p].pchain].pheap:=p;
p:=l;
end
else
break;
l:=p*2;r:=l+1;
until false;
heap[p]:=heap[h];chain[heap[p].pchain].pheap:=p;
dec(h);
end;
begin
repeat
assign(input,'memory.in');reset(input);
assign(output,'memory.out');rewrite(output);
h:=1;
with heap[1] do begin fin:=maxlongint;pchain:=1;end;
n:=1;
with chain[0] do begin right:=0;next:=1;end;
with chain[1] do begin read(left);pheap:=1;pre:=0;next:=0;end;
f:=1;r:=0;
repeat
read(a,b,c);
if b=0 then a:=maxlongint-1;
while heap[1].fin<=a do begin
repeat now:=pop;until heap[1].fin>now;
while f<=r do begin
p:=where(wait[f].mem);
if chain[p].next=0 then break;
ins(p,now+wait[f].time,wait[f].mem);
inc(f);
end;
end;
if b=0 then break;
p:=where(b);
if chain[p].next>0 then
ins(p,a+c,b)
else begin
inc(r);wait[r].mem:=b;wait[r].time:=c;
end;
until false;
writeln(now);writeln(r);
close(input);close(output);
until seekeof;
end.
【題目描述】
Dino同窗喜歡去購物,可是他不想花不少錢,因此他老是挑那些打折的東西買,如今給出他買的全部東西的一個購物清單,以及每一個物品打幾折,問:他此次購物一共花了多少錢?
【輸入】
第一行一個n(1≤n≤100)表示Dino一共買了多少個東西。後面緊接n行,每行描述購買的一種物品:每行2個整數ai,bi(1≤ai≤10000,1≤bi≤10)。
【輸出】
一行,一個實數爲Dino一共花了多少錢,答案保留2位小數。
【樣例輸入】
2
10000 10
1 1
【樣例輸出】
10000.10
【題目描述】
如圖3-8所示。
1 2 3
4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
31 32 33
圖3-8 棋局示意圖
數字至關於格子的編號,在它們上面有棋子,移動規則是:每次移動一個棋子,這個棋子必須跨越與其相鄰的一個棋子到達一個空格子上(和跳棋相似),且只能橫豎走,不能斜着走,移動完後,中間那個棋子就會被吃掉。
在一個棋局中,有不少棋子能夠移動,那麼移動哪個呢?若把每一個棋子移動後會到達的格子稱爲目標格子,則在這些目標格子中選擇編號最大的一個,再在能達到此格子的棋子中選擇編號最大的一個來移動。通過若干次移動後,就會出現不能移動的狀況。此時,輸出全部棋子所在編號的和。輸入會告訴你哪些格子上有棋子,以0結束(不必定一行一組數據)。
【樣例輸入】
4
10 12 17 19 25 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 0
【樣例輸出】
HI Q OUTPUT
51
0
561
98
END OF OUTPUT
從問題的某一個初始解出發逐步逼近給定的目標,以儘量快的地求得更好的解。當達到某算法中的某一步不能再繼續前進時,算法中止。該算法存在的問題:
(1)不能保證求得的最後解是最佳的。
(2)不能用來求最大或最小解問題。
(3)只能求知足某些約束條件的可行解的範圍。
實現該算法的過程:
(1)從問題的某一初始解出發。
(2)while 能朝給定總目標前進一步 do 求出可行解的一個解元素。
(3)由全部解元素組合成問題的一個可行解。
(4)貪心:找出全部度爲1的結點,把與它們相連的結點上都放上士兵,而後把這些度爲1的結點及已放上士兵的結點都去掉。重複上述過程直至數空爲止。
貪心算法的優勢在於時間複雜度極低。貪心算法與其餘最優化算法的區別在於:它具備不可後撤性,能夠有後效性,通常狀況下不知足最優化原理。貪心算法的特色就決定了它的適用範圍,他通常不適用於解決可行性問題,僅適用於較容易獲得可行解的最優性問題。這裏較容易獲得可行解的概念是:當前的策略選擇後,不會或極少使後面出現無解的狀況。另外,對於近年來出現的交互性題目,貪心算法是一個較好的選擇。這是由於在題目中,一個策略的結果是隨題目的進行而逐漸給出的,咱們沒法預先知道所選策略的結果,這與貪心算法不考慮策略的結果和其具備後效性的特色是不謀而合的。固然,貪心算法還能夠爲搜索算法提供較優的初始界值。
儘管貪心算法有必定的優越性,但它畢竟在通常狀況下得不到最優解。所以,爲了儘可能減少貪心算法帶來的反作用,使得最後獲得的解更接近最優解,應該在算法儘量多的地方使用有效的最優化算法(如動態規劃)。
貪心算法的缺點在於解的效果比較差,而最大優點在於極低的時間複雜度,並且每每時間複雜度遠遠低於題目的限制。那麼,咱們爲何再也不花一部分時間來提升目標解的效果呢?這就是對貪心算法改進的必要性。
例1 智力大沖浪(riddle.pas)。
【題目描述】
小偉報名參加中央電視臺的智力大沖浪節目。本次挑戰賽吸引了衆多參賽者,主持人爲了表彰你們的勇氣,先獎勵每一個參賽者m元。先不要過高興!由於這些錢還不必定都是你的。接下來主持人宣佈了比賽規則:
首先,比賽時間分爲n個時段(n≤500),它又給出了不少小遊戲,每一個小遊戲都必須在規按期限ti前完成(1≤ti≤n)。若是一個遊戲沒能在規按期限前完成,則要從獎勵費m元中扣去一部分錢wi,wi爲天然數,不一樣的遊戲扣去的錢是不同的。固然,每一個遊戲自己都很簡單,保證每一個參賽者都能在一個時段內完成,並且都必須從整時段開始。主持人只是想考考每一個參賽者如何安排組織本身作遊戲的順序。做爲參賽者,小偉很想贏得冠軍,固然更想贏取最多的錢!
注意:比賽絕對不會讓參賽者賠錢!
【輸入】
輸入文件riddle.in,共4行。
第一行爲m,表示一開始獎勵給每位參賽者的錢;
第二行爲n,表示有n個小遊戲;
第三行有n個數,分別表示遊戲1~n的規定完成期限;
第四行有n個數,分別表示遊戲1~n不能在規按期限前完成的扣款數。
【輸出】
輸出文件riddle.out,僅1行。表示小偉能贏取最多的錢。
【樣例輸入】
10000
7
4 2 4 3 1 4 6
70 60 50 40 30 20 10
【樣例輸出】
9950
【算法分析】
由於不一樣的小遊戲不能準時完成時具備不一樣的扣款權數,並且是最優解問題,因此本題很容易就想到了貪心法。貪心的主要思想是要讓扣款數值大的儘可能準時完成。這樣咱們就先把這些任務按照扣款的數目進行排序,把大的排在前面,先進行放置。假如罰款最多的一個任務的完成期限是k,咱們應該把它安排在哪一個時段完成呢?應該放在第k個時段,由於放在1~k任意一個位置,效果都是同樣的。一旦出現一個不可能在規定時限前完成的任務,則把其扔到最大的一個空時間段,這樣必然是最優的,由於不能完成的任務,在任意一個時間段中罰款數目都是同樣的,具體實現請參考下面的[程序1]。
本題也能夠有另一種貪心算法,即先把全部的數據按照結束時間的前後排序,而後從前向後掃描。當掃描到第n個時段,發現裏面所分配任務的結束時間等於n-1,那麼就說明在前面這些任務中必須捨棄一個,因而再掃描第1~n這n個時段,挑出一個最小的去掉並累加扣款值,而後再去調整排列順序,讓後面的元素填補前面的空缺,具體實現參考下面 的[程序2]。
[程序1]
program riddle1 (input, output);
var i,j,k,n,s,m:longint;
boo:boolean;
a,b:array[1..100] of longint;
hash:array[1..100] of boolean;
procedure sort;
var p,q:longint;
begin
for i:=1 to n-1 do
for j:=i+1 to n do
if b[i]<b[j] then
begin p:=b[i];b[i]:=b[j];b[j]:=p;
p:=a[i];a[i]:=a[j];a[j]:=p;end;
end;
begin
fillchar(hash,sizeof(hash),true);
assign(input,'riddle.in');
reset(input);
assign(output,'riddle.out');
rewrite(output);
readln(m);
readln(n);
for i:=1 to n do
read(a[i]);
for i:=1 to n do
read(b[i]);
sort;
for i:=1 to n do
begin
boo:=true;
for j:=a[i] downto 1 do
if hash[j] then begin boo:=false;hash[j]:=false;break;end;
if boo then begin
for k:=n downto 1 do
if hash[k] then begin hash[k]:=false;break;end;
inc(s,b[i]);
end;
end;
writeln(m-s);
close(input);
close(output);
end.
[程序2]
program riddle2(input,output);
var
m,n,p,min,minx,total:longint;
w,t:array[1..100] of longint;
i,j:longint;
begin
assign(input,'riddle.in');
assign(output,'riddle.out');
reset(input);
rewrite(output);
readln(m);
readln(n);
for i:=1 to n do
read(t[i]);
for i:=1 to n do
read(w[i]);
for i:=1 to n-1 do
for j:=i+1 to n do
if (t[i]>t[j]) or (t[i]=t[j]) and (w[i]>w[j]) then
begin
p:=t[i];
t[i]:=t[j];
t[j]:=p;
p:=w[i];
w[i]:=w[j];
w[j]:=p;
end;
for i:=1 to n do
if (t[i]<i) and (i<=n) then
begin
min:=maxlongint;
for j:=1 to i do
if w[j]<min then
begin
min:=w[j];
minx:=j;
end;
total:=total+min;
for j:=minx to n-1 do
begin
w[j]:=w[j+1];
t[j]:=t[j+1];
end;
dec(n);
dec(i);
end;
writeln(m-total);
close(input);
close(output);
end.
例2 合併果子(fruit.pas)。
【題目描述】
在一個果園裏,多多已經將全部的果子打了下來,並且按果子的不一樣種類分紅了不一樣的堆。多多決定把全部的果子合成一堆。每一次合併,多多能夠把兩堆果子合併到一塊兒,消耗的體力等於兩堆果子的重量之和。能夠看出,全部的果子通過n-1次合併以後,就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。
由於還要花大力氣把這些果子搬回家,因此多多在合併果子時要儘量地節省體力。假定每一個果子重量都爲1,而且已知果子的種類數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。
例如,有3種果子,數目依次爲1、二、9。能夠先將1、2堆合併,新堆數目爲3,耗費體力爲3。接着,將新堆與原先的第三堆合併,又獲得新的堆,數目爲12,耗費體力爲12。因此多多總共耗費體力15=3+12。能夠證實15爲最小的體力耗費值。
【輸入】
輸入文件fruit.in包括兩行。第一行是一個整數n(1≤n≤10000),表示果子的種類數。第二行包含n個整數,用空格分隔,第i個整數ai(1≤ai≤20000)是第i種果子的數目。
【輸出】
輸出文件fruit.out包括一行,這一行只包含一個整數,也就是最小的體力耗費值。輸入數據保證這個值小於231。
【樣例輸入】
3
1 2 9
【樣例輸出】
15
【數據規模】
對於30%的數據,保證有n≤1000;
對於50%的數據,保證有n≤5000;
對於所有的數據,保證有n≤10000。
【算法分析】
此題用貪心法。先將果子數排序,取其中最小的兩堆合併,獲得一個新堆;再排序,再取其中最小的兩堆合併……直到只剩一堆。爲儘快出解,排序的速度顯得格外重要,可用快速排序算法。
[程序]
program pruit
var
n,i,em:word;
heap:array[1..10001]of dword;
sum:dword;
w,a,b:dword;
procedure add (w:dword);
var
i:word;
sw:dword;
begin
i:=em;
heap[i]:=w;
while (i>1) and (heap[i div 2]>heap[i]) do
begin
sw:=heap[i div 2];
heap[i div 2]:=heap[i];
heap[i]:=sw;
i:=i div 2;
end;
while heap[em]<>0 do
inc(em);
end;
procedure swap (s:word);
var
a,b:word;
begin
a:=s*2;
b:=s*2+1;
if (b>10001) or (heap[a]=0) and (heap[b]=0) then
begin
heap[s]:=0;
if s<em then
em:=s;
exit;
end;
if heap[a]=0 then
begin
heap[s]:=heap[b];
swap(b);
exit;
end;
if heap[b]=0 then
begin
heap[s]:=heap[a];
swap(a);
exit;
end;
if heap[a]>heap[b] then
begin
heap[s]:=heap[b];
swap(b);
end
else
begin
heap[s]:=heap[a];
swap(a);
end;
end;
function pop:dword;
begin
pop:=heap[1];
swap(1);
end;
begin
assign(input,'fruit.in');
assign(output,'fruit.out');
reset(input);
rewrite(output);
readln(n);
em:=1;
sum:=0;
fillchar(heap,sizeof(heap),0);
for i:=1 to n do
begin
read(w);
add(w);
end;
readln;
for i:=1 to n-1 do
begin
a:=pop;
b:=pop;
inc(sum,a+b);
add(a+b);
end;
writeln(sum);
close(input);close(output);
end.
例3 建築搶修(repair.pas)。
【題目描述】
小剛在玩JSOI提供的一個稱之爲「建築搶修」的電腦遊戲。通過了一場激烈的戰鬥,T部落消滅了全部Z部落的入侵者。可是T部落的基地裏已經有N個建築設施受到了嚴重的損傷,若是不盡快修復的話,這些建築設施將會徹底毀壞。
如今的狀況是:T部落基地裏只有一個修理工人。雖然他能瞬間到達任何一個建築,可是修復每一個建築都須要必定的時間。同時,修理工人修理完一個建築才能修理下一個建築,不能同時修理多個建築。若是某個建築在一段時間以內沒有徹底修理完畢,這個建築就報廢了。
你的任務是幫小剛合理制定一個修理順序,以搶修儘量多的建築。
【輸入】
輸入文件第一行是一個整數N,N行每行兩個整數T1、T2描述一個建築:修理這個建築須要T1秒,若是在T2秒以內尚未修理完成,這個建築就報廢了。
【輸出】
輸出文件只有一行,是一個整數S,表示最多能夠搶修S個建築。
N<150000; T1<T2<maxlongint
【樣例輸入】
4
100 200
200 1300
1000 1250
2000 3200
【樣例輸出】
3
【算法分析】
貪心 O(N Log N) + 高級數據結構。很容易想到動態規劃。按截止時間排序,維護隊列q,若是能插入就插入,如不能插入,就把一個花費時間最大的替換下來。
[程序]
program repair;
const inf='repair.in'; ouf='repair.out';maxn=150000+10;
type PIT=^Pnode;
Pnode=record l,r:PIT; s,k:longint; end;
var f,g,a,b: array [0..maxn] of longint;
i,j,n,ans,now,x,e: longint;
p,t: PIT;
procedure Swap(var a,b:longint);
var c:longint;
begin
c:=a; a:=b; b:=c;
end;
procedure Qsort(l,r:longint);
var i,j,p:longint;
begin
i:=l; j:=r; p:=b[(i+j)>>1];
repeat
while b[i]<p do inc(i);
while b[j]>p do dec(j);
if i<=j then
begin
swap(b[i],b[j]);
swap(a[i],a[j]);
inc(i); dec(j);
end;
until i>j;
if l<j then Qsort(l,j);
if i<r then Qsort(i,r);
end;
procedure Make(var p:PIT);
begin
p^.l:=nil; p^.r:=nil; p^.s:=0; p^.k:=0;
end;
procedure Ins(t:PIT; l,r,k:longint);
begin
inc(t^.s);
if l=r then begin if t^.k=0 then t^.k:=k; exit; end;
if k<=(l+r)>>1 then
begin
if t^.l=nil then begin new(p); make(p); t^.l:=p; end;
ins(t^.l,l,(l+r)>>1,k);
end else
begin
if t^.r=nil then begin new(p); make(p); t^.r:=p; end;
ins(t^.r,(l+r)>>1+1,r,k);
end;
end;
procedure Del(t:PIT; l,r,k:longint);
begin
if t=nil then exit;
dec(t^.s); if l=r then exit;
if k<=(l+r)>>1 then del(t^.l,l,(l+r)>>1,k) else del(t^.r,(l+r)>>1+1,r,k);
end;
function RFS(t:PIT; l,r,k:longint):longint;
var d:longint;
begin
if t=nil then exit(-1);
if l=r then exit(t^.k);
if t^.l=nil then d:=0 else d:=t^.l^.s;
if k<=d then exit(RFS(t^.l,l,(l+r)>>1,k))
else exit(RFS(t^.r,(l+r)>>1+1,r,k-d));
end;
begin
assign(input,inf); reset(input); assign(output,ouf); rewrite(output);
readln(n);
for i:= 1 to n do readln(a[i],b[i]);
qsort(1,n);new(t); make(t); e:=maxlongint;
for i:= 1 to n do
begin
if now+a[i]<b[i] then
begin
inc(now,a[i]);inc(ans);
ins(t,0,e,a[i]);
end else
begin
x:=rfs(t,0,e,t^.s);
if x>a[i] then
begin
dec(now,x);
inc(now,a[i]);
del(t,0,e,x);
ins(t,0,e,a[i]);
end;
end;
end;
writeln(ans);
close(input); close(output);
end.
艾藝從小酷愛藝術,他夢想成爲一名偉大的藝術家。最近他得到了一塊材質不錯的木板,其下側爲直線段,長爲L,平均分爲L段,從左到右編號爲1,2,…,L。木板的上側爲鋸齒形,高度爲整數,第i段的高度爲Ai,Ai≥2(如圖3-8所示)。
這麼好的一段材料浪費了怪惋惜的,艾藝決定好好加工一番作成一件藝術品。但他不是純藝術家,他以爲每件藝術品都應有實用價值(不然只是華而不實),具備實用性的藝術品是他設計的理念。
根據這塊木板的鋸齒狀,艾藝想到了天天起牀後都要用到的一件日用品,便想把它作成梳子!他的設想是:用刻刀將某些上端的格子挖掉(若是把某個格子挖掉,那麼這個格子上方的格子也必須被挖掉,但不能把一列中的格子全都挖掉),使得剩下的木板構成「規則鋸齒形」(這樣纔好梳頭)。
如圖3-9所示,挖掉第3、7、8列最上面的1個格子和第5列最上面的2個格子後,剩下的區域就構成「規則鋸齒形」(如圖3-10所示)。一個鋸齒形稱爲「規則鋸齒形」當且僅當它的上邊界(圖3-10中紅色曲線所示)的拐點序列不包含「010」或者「101」。圖3-9中紅色曲線的拐點序列爲:「011001」,(其中0表明左拐,1表明右拐),沿着曲線的最左端往右走,先左拐,再右拐,接着右拐,而後左拐,繼續左拐,最後右拐。
爲了最大限度地減小浪費,艾藝但願作出來的梳子面積最大。
圖3-9 木梳題示意圖(1) 圖3-10 木梳題示意圖(2)
【輸入】
從文件input.txt中讀入數據,文件第一行爲整數L,其中4≤L≤100000,且有50%的數據知足L≤104,表示木板下側直線段的長。第二行爲L個正整數A1,A2,…,AL,其中1<Ai≤108,表示第i段的高度。
【輸出】
輸出文件output.txt中僅包含一個整數D,表示爲使梳子面積最大,須要從木板上挖掉的格子數。
【樣例輸入】
9
4 4 6 5 4 2 3 3 5(方案如圖3-11所示)
圖3-11 木梳題示意圖(3)
【樣例輸出】
3
編程學到如今才真正到了高深部分,從這裏往下學,你才知道什麼叫作博大精深。今天咱們要啃的這塊硬骨頭叫作深度優先搜索法。
首先咱們來想象一隻老鼠,在一座不見天日的迷宮內,老鼠在入口處進去,要從出口出來。那老鼠會怎麼走?固然是這樣的:老鼠若是遇到直路,就一直往前走;若是遇到分叉路口,就職意選擇其中的一個繼續往下走;若是遇到死衚衕,就退回到最近的一個分叉路口,選擇另外一條道路再走下去;若是遇到了出口,老鼠的旅途就算結束了。深度優先搜索法的基本原則就是這樣:按照某種條件往前試探搜索,若是前進中遭到失敗(正如老鼠遇到死衚衕)則退回頭另選通路繼續搜索,直到找到條件的目標爲止。
實現這一算法,咱們要用到編程的另外一大利器——遞歸。遞歸是一個很抽象的概念,可是在平常生活中,咱們仍是可以看到的。拿兩面鏡子來,把他們面對着面,你會看到什麼?你會看到鏡子中有無數個鏡子?怎麼回事?A鏡子中有B鏡子的像,B鏡子中有A鏡子的像,A鏡子的像就是A鏡子自己的真實寫照,也就是說A鏡子的像包括了A鏡子,還有B鏡子在A鏡子中的像……好累啊,又煩又繞口,還很差理解。換成計算機語言就是A調用B,而B又調用A,這樣間接的,A就調用了A自己,這實現了一個重複的功能。
他再舉一個例子:從前有座山,山裏有座廟,廟裏有個老和尚,老和尚在講故事,講什麼呢?講:從前有座山,山裏有座廟,廟裏有個老和尚,老和尚在講故事,講什麼呢?他講:從前有座山,山裏有座廟,廟裏有個老和尚,老和尚在講故事,講什麼呢?他講:……。好傢伙!這樣講到世界末日還講不完,老和尚講的故事實際上就是前面的故事情節,這樣不斷地調用程序自己,就造成了遞歸。
萬一這個故事中的某一個老和尚看這個故事不順眼,就把他要講的故事換成:「你有完沒完啊!」,這樣,整個故事也就嘎然而止了。咱們編程就要注意這一點,在適當的時候,就必需要有一個這樣的和尚自告奮勇,把整個故事給停下來,或者使他再也不往深一層次搜索。要不,咱們的遞歸就會因計算機存儲容量的限制而被迫溢出。切記!切記!!
咱們把遞歸思想運用到上面的迷宮中,記老鼠如今所在的位置是(x,y),那它如今有先後左右四個方向能夠走,分別是(x+1,y),(x-1,y),(x,y+1),(x,y-1),其中一個方向是它來時的路,先不考慮,咱們就分別嘗試其餘3個方向,若是某個方向是路而不是牆的話,老鼠就向那個方向邁出一步。在新的位置上,咱們又能夠重複前面的步驟。老鼠走到了死衚衕又是怎麼回事?就是除了來時的路,其餘3個方向都是牆,這時這條路就走到了盡頭,沒法再向深一層發展,咱們就應該沿來時的路回去,嘗試另外的方向。
例1 8皇后問題。
在標準國際象棋的棋盤上(8×8格)準備放置8位皇后,咱們知道,國際象棋中皇后的威力是最大的,她既能夠橫走豎走,還能夠斜着走,遇到擋在她前進路線上的敵人,她就能夠吃掉對手。要求在棋盤上安放8位皇后,使她們彼此互相都不能吃到對方,求皇后的放法。
這是一道很經典的題目了,咱們先要明確一下思路,如何運用深度優先搜索法,完成這道題目。首先創建一個8×8格的棋盤,在棋盤的第一行的任意位置安放一隻皇后。緊接着,咱們就來放第二行,第二行的安放就要受一些限制了,由於與第一行的皇后在同一豎行或同一對角線的位置上是不能安放皇后的,接下來是第三行,……,或許咱們會遇到這種狀況,在擺到某一行的時候,不管皇后擺放在什麼位置,她都會被其餘行的皇后吃掉,這說明什麼呢?這說明,咱們前面的擺放是失敗的,也就是說,按照前面的皇后的擺放方法,咱們不可能獲得正確的解。那這時怎麼辦?改啊!咱們回到上一行,把原先擺好的皇后換另一個位置,接着再回過頭擺這一行,若是這樣還不行或者上一行的皇后只有一個位置可放,那怎麼辦?咱們回到上一行的上一行,這和老鼠碰了壁就回頭是一個意思。就這樣的不斷地嘗試、修正,再嘗試、再修正,咱們最終會獲得正確的結論。
[程序]
program queen; {8皇后問題參考程序}
const n=8;
var a,b:array [1..n] of integer;{數組a存放解:a[i]表示第i個皇后放在第a[i]列;}
c:array [1-n,n-1] of integer;
d:array [2..n+n] of integer; {數組b,c,d表示棋盤的當前狀況:b[k]爲1表示第k行已被佔領爲0表示爲空;c、d表示對角線}
k:integer;
procedure print; {打印結果}
var j:integer;
begin
for j:=1 to n do write(a[j]:4);
writeln;
end;
procedrue try(i:integer); {遞歸搜索解}
var j:integer; {每一個皇后的可放置位置。注意:必定要在過程當中定義;不然當遞歸時會覆蓋掉它的值,不能獲得正確結果}
begin
for j:=1 to n do
begin
if (b[j]=0) and (c[i-j]=0) and (d[i+j]=0) then{檢查位置是否合法}
begin
a[i]:=j; {置第i個皇后的位置是第j行}
b[j]:=1; {宣佈佔領行、對角線}
c[i-j]:=1;
d[i+j]:=1;
if i<n then try(i+1) else print;{若是未達目標則放置下一皇后,不然打印結果}
b[j]:=0; {清空被佔行、對角線,回溯}
c[i-j]:=0;
d[i+j]:=0;
end;
end;
end;
begin
for k:=1 to n do b[k]:=0; {初始化數據}
for k:=1-n to n-1 do c[k]:=0;
for k:=2 to n+n do d[k]:=0;
try(1);
end.
在深度優先搜索中,也是從初始結點開始擴展,可是擴展順序卻不一樣,深度優先搜索老是先擴展最新產生的結點。這就使得搜索沿着狀態空間某條單一的路徑進行下去,直到最後的結點不能產生新結點或者找到目標結點爲止。當搜索到不能產生新的結點的時候,就沿着結點產生順序的反方向尋找能夠產生新結點的結點,並擴展它,造成另外一條搜索路徑。
深度優先搜索中擴展結點的原則是先產生的後擴展。所以,深度優先搜索第一個找到的解,並不必定是問題的最優解,要搜索完整個狀態空間,才能肯定哪一個解是最優解。
深度優先搜索的算法以下:
(1)從初始結點開始,將待擴展結點依次放到open表中。open表爲棧結構,即遵照後進先出的規則。
(2)若是open表空,即全部待擴展結點已所有擴展完畢,則問題無解,退出。
(3)取open表中最新加入的結點,即棧頂結點出棧,並用相應的算符擴展出全部的予結點,並按順序將這些結點放入open表中。若沒有子結點產生,則轉(2)。
(4)若是某個子結點爲目標結點,則找到問題的解(這不必定是最優解),結束。若是要求得問題的最優解,或者全部解,則轉(2),繼續搜索新的目標結點。
【深度優先搜索的算法】
procedure DFS;
begin
open ←1; data[open] .state ←初始狀態;
Search Success ← false;
repeat
currentnode ←data[open];
open ←open-l;
while current {還能夠擴展} do begin
new ← operate (currentnode.state);
if new {重複於已有的結點狀態} then continue;
open ← open+l;
data[open].state ← new;
data[open].last ← currentnode;
if new {是目標狀態} then begin Search Success ← true;break;end;
end;
until (open<l) or (Search Success);
if Search Success then Output(Reslut)
else 0utput(No Answer);
end;
例2 選數(select.pas)。
【題目描述】
已知n(1≤n≤20)個整數x1,x2,…,xn(1≤xi≤5000000),以及一個整數k(k<n)。從n個整數中任選k個整數相加,可分別獲得一系列的和。如今,要求你計算出和爲素數共有多少種。
例如,當n=4,k=3,4個整數分別爲3、7、12、19時,可得所有的組合與它們的和爲:3+7+12=22;3+7+19=29;7+12+19=38;3+12+19=34。
如今,要求你計算出和爲素數共有多少種。
例如,例2只有一種和爲素數:3+7+19=29。
【輸入】
格式爲:
n , k (1<=n<=20,k<n)
x1,x2,…,xn (1<=xi<=5000000)
【輸出】
一個整數(知足條件的種數)。
【樣例輸入】
4 3
3 7 12 19
【樣例輸出】
1
【算法分析】
本題無數學公式可尋,看來只能搜索(組合的生成算法),其實1≤n≤20這個約束條件也暗示咱們本題搜索是有但願的,組合的生成可用簡單的DFS來實現,既搜索這k個整數在原數列中的位置,因爲組合不一樣於排列,與這k個數的排列順序無關,因此,能夠令a[I]<a[I+1](a[I]表示第I個數在原數列中的位置),這個組合生成算法的複雜度大約爲C(n,k),下面給出遞歸搜索算法的框架。
Proc Search(dep) Begin for i <- a[dep - 1] + 1 to N - (M - dep) do 1. a[dep] <- i 2. S <- S + x[i] 3. if dep < k then Search(dep + 1) else 判斷素數 4. S <- S - x[i] End |
接下來的問題就是判斷素數,判斷一個整數P(P>1)是否爲素數最簡單的方法就是看是否存在一個素數a(a≤sqrt(P))是P的約數,若是不存在,該數就爲素數。因爲在此題中1≤xi≤5000000,n≤20,因此要判斷的數P不會超過100000000,sqrt(p)≤10000,所以,爲了加快速度,咱們能夠用篩選法將2~10000之間的素數保存到一個數組裏(共1229個),這樣速度估計將提升5~6倍。
注意:本題是要求使和爲素數的狀況有多少種,並非求有多少種素數,比賽時就有不少同窗胡亂判重而丟了12分;還有1不是素數,在判素數時要對1作特殊處理。
[程序]
program select
const
MaxN = 20;
var
N, M, i: Byte;
ans, s: Longint;
x: array[1 .. MaxN] of Longint;
f: array[1 .. 10000] of Byte;
p: array[1 .. 1229] of Integer;
procedure Get_Prime;
var
i, j, s: Integer;
begin
s := 0;
f[1] := 0;
for i := 2 to 10000 do f[i] := 1;
for i := 2 to 10000 do
if f[i] = 1 then
begin
Inc(s); p[s] := i;
j := 2 * i;
while j <= 10000 do begin f[j] := 0; Inc(j, i) end;
end
end;
procedure Work(S: Longint);
var
i: Integer;
begin
if S <= 10000 then Inc(ans, f[S])
else
begin
i := 1;
while sqr(longint(p[i])) <= S do
begin
if S mod p[i] = 0 then Exit;
Inc(i)
end;
Inc(ans)
end
end;
procedure Search(d, pre: Byte);
var
i: Byte;
begin
for i := pre + 1 to N - (M - d) do
begin
Inc(S, x[i]);
if d = M then Work(S)
else Search(d + 1, i);
Dec(S, x[i])
end
end;
begin
assign(input,'select.in');reset(input);
assign(output,'select.out');rewrite(output);
readln(N, M);
for i := 1 to N do Read(x[i]);
ans := 0; S := 0;
Get_Prime;
Search(1, 0);
Writeln(ans);
close(input);close(output);
end.
例3 附加的等式(equati.pas)。
【樣例輸入】
3 [3個測試數據]
3 1 2 3 [共有3個數參加運算]
3 1 2 5
6 1 2 3 5 4 6 [共有6個數參加運算]
【樣例輸出】
1+2=3[參加運算的3個數能構成的加法運算,打印完結果以後要輸出一個空行與下面的結果隔開]
Can't find any equations. [不能構成任何加法運算等式]
1+2=3
1+3=4
1+4=5
1+5=6
2+3=5
2+4=6
1+2+3=6
注意:若是能構成多個加法運算等式,那麼打印時要進行一下排列。
等式左邊的數字個數小的放在前面,若是個數同樣多,則按數字的值進行排列,從左到右,例如,1+2=3與1+3=4。爲何1+2=3要放在前面呢?由於(1,2)與(1,3)相比,在第一個數都爲1的狀況下,前者的第二個數要小於後者的第二個數。
【算法分析】
面對這道題要勇敢,不要想太多。直接用控制深度的DFS搜便可(加剪枝)。要用組合公式。
[程序]
var a,b,que:array [1..100] of longint;
n,m,i,j,k,p,q,sum,rear,max:longint;
done:boolean;
procedure qsort(l,r:longint);
var i,j,mid,t:longint;
begin
i:=l;j:=r;mid:=a[(l+r)shr 1];
repeat
while a[i]<mid do inc(i);
while a[j]>mid do dec(j);
if i<=j then
begin
t:=a[i];a[i]:=a[j];a[j]:=t;
inc(i);dec(j);
end;
until i>j;
if l<j then qsort(l,j);
if i<r then qsort(i,r);
end;
procedure choose(s,e,d,i:longint);
var j:longint;
begin
if i<d then
for j:=s to e do
begin
if (sum+a[j])<=max then
begin
inc(sum,a[j]);
inc(rear);
que[rear]:=a[j];
choose(j+1,e+1,d,i+1);
dec(sum,a[j]);
dec(rear);
end
end
else
begin
for j:=s to e do
if j<=n then
if sum=a[j] then
begin
done:=true;
for q:=1 to d-1 do
begin
write(que[q]);
if q<>d-1 then write('+');
end;
writeln('=',a[j]);
end;
end;
end;
procedure main(d:longint);
var i,j:longint;
begin
sum:=0; rear:=0;
choose(1,n-d+1,d,1);
if d<>n then main(d+1);
end;
begin
assign(input,'equati.in'); reset(input);
assign(output,'equati.out'); rewrite(output);
readln(m);
for i:= 1 to m do
begin
done:=false;
read(n);
for j:= 1 to n do read(a[j]); readln;
qsort(1,n);
max:=a[n];
main(3);
if not done then writeln('Can''t find any equations.');
writeln;
end;
close(input); close(output);
end.
【題目描述】
7月17日是Mr.W的生日,ACM-THU爲此要製做一個體積爲Nπ的M層生日蛋糕,每層都是一個圓柱體(如圖3-12所示)。
設從下往上數第i(1≤i≤M)層蛋糕是半徑爲Ri, 高度爲Hi的圓柱。當i<M時,要求Ri>Ri+1且Hi>Hi+1。因爲要在蛋糕上抹奶油,爲儘量節約經費,咱們但願蛋糕外表面(最下一層的下底面除外)的面積Q最小。(令Q= Sπ)
圖3-12 生日蛋糕圖
編程,對給出的N和M,找出蛋糕的製做方案(適當的Ri和Hi的值),使S最小。(除Q外,以上全部數據皆爲正整數)
【輸入】
有兩行,第一行爲N(N≤10000),表示待制做的蛋糕的體積爲Nπ;第二行爲M(M≤20),表示蛋糕的層數爲M。
【輸出】
僅一行,是一個正整數S(若無解則S=0)。
【樣例輸入】
100
2
【樣例輸出】
68
(附圓柱公式:體積V=πR2H;側面積A’=2πRH;底面積A=πR2 )
廣度優先搜索是從初始結點開始,應用算符生成第一層結點,同時檢查目標結點是否在這些生成的結點中。若沒有,再用算符將全部第一層的結點逐一擴展,獲得第二層結點,並逐一檢查第二層結點中是否包含目標結點。若沒有,再用算符逐一擴展第二層的全部結點……,如此依次擴展,檢查下去,直至發現目標結點爲止。若是擴展完全部的結點,都沒有發現目標結點,則問題無解。
在搜索的過程當中,廣度優先搜索對於結點是沿着深度的斷層擴展的。若是要擴展第n+1層的結點,必須先所有擴展完第n層的結點。那麼,對於同一層結點來講,對於問題求解的價值是相同的。因此,這種搜索方法必定能保證找到最短的解序列。也就是說,第一個找到的目標結點,必定是應用算符最少的。所以,廣度優先搜索算法比較適合求最少步驟或者最短解序列的題目。
一般,廣度優先搜索需用隊列來幫助實現。
(1)process(state)
(2)for each possible next state from this one
(3)enqueue next state
(4)search()
(5)enqueue initial state
(6)while !empty(queue)
(7)state = get state from queue
(8)process(state)
廣度優先搜索得名於它的實現方式:每次都先將搜索樹某一層的全部節點所有訪問完畢後再訪問下一層,再利用先前的那棵搜索樹。廣度優先搜索以如圖3-13所示順序遍歷。
首先,訪問根節點;然後是搜索樹第一層的全部節點;最後第二層、第三層……依次類推。
廣度優先搜索中的結點是先產生的先擴展,而深度優先搜索中擴展結點的原則是先產生的後擴展,這兩種搜索方式的本質不一樣。
圖3-13 廣度優先搜索順序示意圖
例1 遊戲(ahjo.pas)。
【題目描述】
在一種「麻將」遊戲中,遊戲是在一個有W×H格子的矩形平板上進行的。每一個格子能夠放置一個麻將牌,也能夠不放(如圖3-14所示)。玩家的目標是將平板上的全部可經過一條路徑相連的兩張相同的麻將牌,從平板上移去。最後,若是能將全部牌移出平板,則算過關。
這個遊戲中的一個關鍵問題是:兩張牌之間是否能夠被一條路徑所鏈接,該路徑知足如下兩個特性:
(1)它由若干條線段組成,每條線段要麼是水平方向,要麼是垂直方向。
(2)這條路徑不能橫穿任何一個麻將牌(但容許路徑暫時離開平板)。
圖3-14 麻將遊戲圖
這是一個例子:在(1,3)的牌和在(4,4)的牌能夠被鏈接。(2,3)和(3,4)不能被鏈接。
你的任務是編一個程序,檢測兩張牌是否能被一條符合以上規定的路徑所鏈接。
【輸入】
第一行爲一整數N表示有N組測試數據。每組測試數據的第一行有兩個整數w,h (1≤w,h≤75),表示平板的寬和高。接下來h行描述平板信息,每行包含w個字符,若是某格子有一張牌,則這個格子上有個‘X’,不然是一個空格。
平板上最左上角格子的座標爲(1,1),最右下角格子的座標爲(w,h)。接下來的若干行,每行有四個數x1,y1,x2,y2,且知足1≤x1,x2≤w,1≤y1,y2≤h,表示兩張牌的座標(這兩張牌的座標老是不一樣的)。若是出現連續四個0,則表示輸入結束。
【輸出】
輸出文件中,對於每一對牌輸出佔一行,爲鏈接這一對牌的路徑最少包含的線段數。若是不存在路徑則輸出0。
【樣例輸入】
1
5 4
XXXXX
X X
XXX X
XXX
2 3 5 3
1 3 4 4
2 3 3 4
0 0 0 0
【樣例輸出】
4
3
0
【算法分析】
從起點出發,每次擴展就是沿上下左右四個方向闖下去,直到碰到一張牌或者邊界爲止。這裏的「邊界」應是原棋盤再往外的一圈,由於連線能夠超出棋盤。若是碰到的牌正好是終點,那麼BFS成功。另外還要注意,輸出的應是路徑上線段的條數,而不是拐的彎數。
[程序]
program mahjo;
const
maxh=75;
maxw=75;
var
map:array[-1..maxh+2,-1..maxw+2]of char;
qx,qy:array[1..(maxh+4)*(maxw+4)+1]of shortint;
seg:array[-1..maxh+2,-1..maxw+2]of word;
h,w,i,j,a,b:shortint;
n,t,f,r,s:word;
procedure add(x,y:shortint;s:word);
begin
if seg[x,y]=0 then begin
inc(r);qx[r]:=x;qy[r]:=y;seg[x,y]:=s;
end;
end;
begin
assign(input,'mahjo.in');reset(input);
assign(output,'mahjo.out');rewrite(output);
read(n);
for t:=1 to n do begin
read(w,h);
for i:=-1 to h+2 do begin map[i,-1]:='X';map[i,0]:=' ';map[i,w+1]:=' ';map[i,w+2]:='X';end;
for i:=0 to w+1 do begin map[-1,i]:='X';map[0,i]:=' ';map[h+1,i]:=' ';map[h+2,i]:='X';end;
for i:=1 to h do begin
readln;
for j:=1 to w do
read(map[i,j]);
end;
repeat
fillchar(seg,sizeof(seg),0);
read(qy[1],qx[1],b,a);
if qx[1]=0 then break;
f:=0;r:=1;
repeat
inc(f);if(f>1)and(map[qx[f],qy[f]]='X')thencontinue;s:=seg[qx[f],
qy[f]]+1;
i:=qx[f];j:=qy[f];repeat dec(i);add(i,j,s);until map[i,j]='X';
i:=qx[f];j:=qy[f];repeat dec(j);add(i,j,s);until map[i,j]='X';
i:=qx[f];j:=qy[f];repeat inc(i);add(i,j,s);until map[i,j]='X';
i:=qx[f];j:=qy[f];repeat inc(j);add(i,j,s);until map[i,j]='X';
until (f=r) or (seg[a,b]>0);
writeln(seg[a,b]);
until false;
end;
close(input);close(output);
end.
例2 冰原探險(ice.pas)。
【題目描述】
傳說中,南極有一片廣闊的冰原,在冰原下藏有史前文明的遺址。整個冰原被橫豎劃分紅了不少個大小相等的方格。在這個冰原上有N個大小不等的矩形冰山,這些巨大的冰山有着和南極同樣古老的歷史,每一個矩形冰山至少佔據一個方格,且其一定完整地佔據方格。冰山和冰山之間不會重疊,也不會有邊或點相連。如下兩種狀況均是不可能出現的(如圖3-15所示)。
(1) (2)
圖3-15 冰原探險示意圖(1)
ACM探險隊在通過多年準備以後決定在這個冰原上尋找遺址。根據他們掌握的資料,在這個冰原上一個大小爲一格的深洞中,藏有一個由史前人類製做的開關。而惟一能夠打開這個開關的是一個佔據接近一格的可移動的小冰塊。顯然,在南極是不可能有這樣小的獨立冰塊的,因此這塊冰塊也必定是史前文明的產物。他們在想辦法把這個冰塊推到洞裏去,這樣就能夠打開一條通往冰原底部的通道,發掘史前文明的祕密。冰塊的起始位置與深洞的位置均不和任何冰山相鄰。
這個冰原上的冰面和冰山都是徹底光滑的,輕輕地推進冰塊就能夠使這個冰塊向前滑行,直到撞到一座冰山就在它的邊上停下來。冰塊能夠穿過冰面上全部沒有冰山的區域,也能夠從兩座冰山之間穿過(如圖3-16所示)。冰塊只能沿網格方向推進。
請你幫助他們以最少的推進次數將冰塊推入深洞中。
(3) (4)
圖3-16 冰原探險示意圖(2)
【輸入】
輸入文件第一行爲冰山的個數N(1≤N≤4000),第二行爲冰塊開始所在的方格座標X1,Y1,第三行爲深洞所在的方格座標X2,Y2,如下N行每行有四個數,分別是每一個冰山所佔的格子左上角和右下角座標Xi1,Yi1,Xi2,Yi2,如圖3-17所示。
【輸出】
輸出文件僅包含一個整數,爲最少推進冰塊的次數。若是沒法將冰塊推入深洞中,則輸出0。
【樣例輸入】
2
1 1
5 5
1 3 3 3
6 2 8 4
【樣例輸出】
3
(5)
圖3-17 冰原探險示意圖(3)
【算法分析】
很明顯,該題應當採起的方法是廣度優先搜索算法。
由於冰山沒有相接的狀況,因此能夠不要記下具體的位置,對於同一個冰山側面的任何位置,朝固定方向推冰塊的效果是同樣的。
這樣在判重的時候也十分簡單,只要用這樣一個數組:already : array[1..4000, 1..4]of boolean。already[i, j]表示第i個冰山的第j個側面是否到達過。
咱們將目的地看成第n+1個冰山,這樣在擴展的時候只要碰到第n+1個冰山就出解了。對冰山按兩個座標軸的方向分別排序,能夠進一步減小擴展時間。
[程序]
program ice;
const
filein = 'ice.in';
fileout = 'ice.out';
up = 1;left = 2;right = 3;down = 4;
move : array[1..4, 1..2]of byte = ((left, right), (up, down), (up, down), (left, right));
move2 : array[1..4, 1..2]of byte = ((up, down), (left, right), (left, right), (up, down));
type
ticeberg = record
x1, y1, x2, y2 : integer;
end;
tstate = record
ibid : word;
bor : byte;
end;
var
already : array[1..4000, 1..4]of boolean;
iceberg : array[1..4001]of ticeberg;
a1, a2 : array[1..1000]of tstate;
step, n, q1, q2 : word;
srcx, srcy, tarx, tary : integer;
time : longint;
procedure initialize;
var f : text; b : boolean; i : word;
begin
assign(f, filein); reset(f);
readln(f, n);
readln(f, srcx, srcy);
readln(f, tarx, tary);
b := true;
for i := 1 to n do
with iceberg[i] do
readln(f, x1, y1, x2, y2);
close(f);
with iceberg[n + 1] do
begin
x1 := tarx; x2 := x1;
y1 := tary; y2 := y1;
end;
end;
procedure out;
var f : text;
begin
assign(f, fileout); rewrite(f);
writeln(f, step);
close(f);
halt;
end;
procedure expandsrc(p : byte; var p1, p2 : word);
var i, j : word;
m1, m2 : integer;
begin
p1 := 0; p2 := 0;
j := 0;
if (p = up) or (p = down) then
begin
m1 := -maxint; m2 := maxint;
for i := 1 to n + 1 do
begin
if (iceberg[i].x1 <= srcx) and (iceberg[i].x2 >= srcx) then
if (iceberg[i].y2 + 1 < srcy) and (iceberg[i].y2 + 1 > m1) then
begin m1 := iceberg[i].y2; p1 := i; end;
if (iceberg[i].x1 <= srcx) and (iceberg[i].x2 >= srcx) then
if (iceberg[i].y1 - 1 > srcy) and (iceberg[i].y1 - 1 < m2) then
begin m2 := iceberg[i].y1; p2 := i; end;
end;
end
else
begin
m1 := -maxint; m2 := maxint;
for i := 1 to n + 1 do
begin
if (iceberg[i].y1 <= srcy) and (iceberg[i].y2 >= srcy) then
if (iceberg[i].x2 + 1 < srcx) and (iceberg[i].x2 + 1 > m1) then
begin m1 := iceberg[i].x2; p1 := i; end;
if (iceberg[i].y1 <= srcy) and (iceberg[i].y2 >= srcy) then
if (iceberg[i].x1 - 1 > srcx) and (iceberg[i].x1 - 1 < m2) then
begin m2 := iceberg[i].x1; p2 := i; end;
end;
end;
if (p1 = n + 1) or (p2 = n + 1) then out;
end;
procedure expand(id : word; q : byte; var p1, p2 : word);
var i : word;
x, y, m1, m2 : integer;
begin
p1 := 0; p2 := 0;
case q of
up : begin x := iceberg[id].x1; y := iceberg[id].y1 - 1; end;
down : begin x := iceberg[id].x2; y := iceberg[id].y2 + 1; end;
right : begin x := iceberg[id].x2 + 1; y := iceberg[id].y2; end;
left : begin x := iceberg[id].x1 - 1; y := iceberg[id].y1; end;
end;
if (q = left) or (q = right) then
begin
m1 := -maxint; m2 := maxint;
for i := 1 to n + 1 do
begin
if (iceberg[i].x1 <= x) and (iceberg[i].x2 >= x) then
if (iceberg[i].y2 + 1 < y) and (iceberg[i].y2 + 1 > m1) then
begin m1 := iceberg[i].y2; p1 := i; end;
if (iceberg[i].x1 <= x) and (iceberg[i].x2 >= x) then
if (iceberg[i].y1 - 1 > y) and (iceberg[i].y1 - 1 < m2) then
begin m2 := iceberg[i].y1; p2 := i; end;
end;
end
else
begin
m1 := -maxint; m2 := maxint;
for i := 1 to n + 1 do
begin
if (iceberg[i].y1 <= y) and (iceberg[i].y2 >= y) then
if (iceberg[i].x2 + 1 < x) and (iceberg[i].x2 + 1 > m1) then
begin m1 := iceberg[i].x2; p1 := i; end;
if (iceberg[i].y1 <= y) and (iceberg[i].y2 >= y) then
if (iceberg[i].x1 - 1 > x) and (iceberg[i].x1 - 1 < m2) then
begin m2 := iceberg[i].x1; p2 := i; end;
end;
end;
if (p1 = n + 1) or (p2 = n + 1) then out;
end;
procedure firstexpand;
var i, b : byte;
next1, next2 : word;
begin
step := 1;
for i := up to left do
begin
expandsrc(i, next1, next2);
b := 5 - move2[i, 1];
if next1 <> 0 then
begin
inc(q1);
a1[q1].ibid := next1;
a1[q1].bor := b;
already[next1, b] := true
end;
b := 5 - move2[i, 2];
if next2 <> 0 then
begin
inc(q1);
a1[q1].ibid := next2;
a1[q1].bor := b;
already[next2, b] := true
end;
end;
end;
procedure mainexpand;
var i : word;
j, b : byte;
next1, next2 : word;
begin
repeat
inc(step);
for i := 1 to q1 do
begin
expand(a1[i].ibid, a1[i].bor, next1, next2);
b := 5 - move[a1[i].bor, 1];
if next1 <> 0 then
if not already[next1, b] then
begin
inc(q2);
a2[q2].ibid := next1;
a2[q2].bor := b;
already[next1, b] := true
end;
b := 5 - move[a1[i].bor, 2];
if next2 <> 0 then
if not already[next2, b] then
begin
inc(q2);
a2[q2].ibid := next2;
a2[q2].bor := b;
already[next2, b] := true
end
end;
if q2 = 0 then break;
a1 := a2; q1 := q2;
q2 := 0;
until false;
end;
procedure outfailed;
var f : text;
begin
assign(f, fileout); rewrite(f);
writeln(f, 0);
close(f);
end;
begin
initialize;
firstexpand;
mainexpand;
outfailed;
end.
【題目描述】
阿蘭是某機密部門的打字員,她接到一個任務:須要在一天以內輸入幾百個長度固定爲6的密碼。固然,她但願輸入的過程當中敲擊鍵盤的總次數越少越好。
不幸的是,出於保密的須要,該部門用於輸入密碼的鍵盤是特殊設計的,鍵盤上沒有數字鍵,而只有如下6個鍵:Swap0、Swap一、Up、Down、Left、Right,爲了說明這6個鍵的做用,咱們先定義錄入區的6個位置的編號,從左至右依次爲1、2、3、4、5、6。下面列出每一個鍵的做用:
Swap0 按Swap0鍵,光標位置不變,將光標所在位置的數字與錄入區的1號位置的數字(左起第一個數字)交換。若是光標已經處在錄入區的1號位置,則按Swap0鍵以後,錄入區的數字不變;
Swap1 按Swap1鍵,光標位置不變,將光標所在位置的數字與錄入區的6號位置的數字(左起第六個數字)交換。若是光標已經處在錄入區的6號位置,則按Swap1鍵以後,錄入區的數字不變;
Up 按Up鍵,光標位置不變,將光標所在位置的數字加1(除非該數字是9)。例如,若是光標所在位置的數字爲2,按Up鍵以後,該處的數字變爲3;若是該處數字爲9,則按Up鍵以後,數字不變,光標位置也不變;
Down 按Down鍵,光標位置不變,將光標所在位置的數字減1(除非該數字是0),若是該處數字爲0,則按Down鍵以後,數字不變,光標位置也不變;
Left 按Left鍵,光標左移一個位置,若是光標已經在錄入區的1號位置(左起第一個位置)上,則光標不動;
Right 按Right鍵,光標右移一個位置,若是光標已經在錄入區的6號位置(左起第六個位置)上,則光標不動。
固然,爲了使這樣的鍵盤發揮做用,每次錄入密碼以前,錄入區總會隨機出現一個長度爲6的初始密碼,並且光標固定出如今1號位置上。當巧妙地使用上述6個特殊鍵以後,能夠獲得目標密碼,這時光標容許停在任何一個位置。
如今,阿蘭須要你的幫助,編寫一個程序,求出錄入一個密碼最少須要的擊鍵次數。
【輸入】
文件僅一行,含有兩個長度爲6的數,前者爲初始密碼,後者爲目標密碼,兩個密碼之間用一個空格隔開。
【輸出】
文件僅一行,含有一個正整數,爲最少須要的擊鍵次數。
【樣例輸入】
123456 654321
【樣例輸出】
11
【題目描述】
Sramoc ( K,M ) 表示用數字0,1,2…,K-1組成的天然數中能被M整除的最小數。給定 K、M,求Sramoc ( K,M )。例如,K=2,M=7的時候,Sramoc(2,7) = 1001。
【輸入】
從文件SRAMOC.IN讀入數據。第一行爲兩個整數K、M知足2≤K≤10、1≤M≤1000。
【輸出】輸出Sramoc(K,M) 到 SRAMOC.OUT。
【樣例輸入】
2 7
【樣例輸出】
1001
所謂雙向搜索指的是搜索沿兩個方向同時進行。正向搜索:從初始結點向目標結點方向搜索;逆向搜索:從目標結點向初始結點方向搜索;當兩個方向的搜索生成同一子結點時終止此搜索過程。
廣度雙向搜索一般有兩種方法:
(1)兩個方向交替擴展;
(2)選擇結點個數較少的那個力向先擴展。方法(2)克服了兩個方向結點的生成速度不平衡的狀態,明顯提升了效率。
設置兩個隊列c:array[0..1,1..maxn] of jid,分別表示正向和逆向的擴展隊列;設置兩個頭指針head:array[0..1] of integer,分別表示正向和逆向當前將擴展結點的頭指針;設置兩個尾指針tail:array[0..1] of integer,分別表示正向和逆向的尾指針。(maxn表示隊列最大長度)
(1)主程序代碼
repeat
{選擇節點數較少且隊列未空、未滿的方向先擴展}
if (tail[0]<=tail[1]) and not((head[0]>=tail[0])or(tail[0]>=maxn)) then expand(0);
if (tail[1]<=tail[0]) and not((head[1]>=tail[1])or(tail[1]>=maxn)) then expand(1);
{若是一方搜索終止,繼續另外一方的搜索,直到兩個方向都終止}
if not((head[0]>=tail[0])or(tail[0]>=maxn)) then expand(0);
if not((head[1]>=tail[1])or(tail[1]>=maxn)) then expand(1);
Until((head[0]>=tail[0])or(tail[0]>=maxn))And ((head[1]>=tail[1])or (tail[1]>=maxn)).
(2)expand(st:0..1)程序代碼
inc(head[st];
取出隊列當前待擴展的結點c[st,head[st]]
for i:=1 to maxk do
begin
if tail[st]>=maxn then exit;
inc(tail[st]);
產生新結點;
check(st);{檢查新節點是否重複}
end;
(3)check(st:0..1)程序代碼
for i:=1 to tail[st]-1 do
if c[st,tail[st]]^.*=c[st,i]^.* then begin dec(tail[st]);exit end;
bool(st);{若是節點不重複,再判斷是否到達目標狀態}
(4)bool(st:0..1)程序代碼
for i:=1 to tail[1-st] do
if c[st,tail[st]]^.*=c[1-st,i]^.* then print(st,tail[st],i);
{若是雙向搜索相遇(即獲得同一節點),則輸出結果}
(5)print(st,tail,k)程序代碼
if st=0 then begin print0(tail);print1(k) end;
else begin print0(k);print1(tail) end;
(6)print0(m)程序代碼
if m<>0 then begin print(c[0,m]^.f);輸出c[0,m]^.* end;
{輸出正方向上產生的結點}
(7)print1(m)程序代碼
n:=c[1,m]^.f
while m<>0
begin
輸出c[1,n]^.*;
n:=c[1,n]^.f;
end
{輸出反方向上產生的結點}
例1 字串變換(word.pas)。
【題目描述】
已知有兩個字串A$,B$及一組字串變換的規則:
A1$→B1$
A2$→B2$
……
規則的含義:在A$中的子串A1$能夠變換爲B1$、A2$能夠變換爲B2$…..。例如:A$ =‘abcd’;B$=‘xyz’。變換規則:‘abc’→‘xu’;‘ud’→‘y’;‘y’→‘yz’。則此時,A$能夠通過一系列的變換變爲B$,其變換的過程爲:‘abcd’→‘xud’→‘xy’→‘xyz’。
共進行了3次變換,使得A$變換爲B$。
【輸入】
輸入文件名爲word .in。
格式: A$,B$
A1$ → B1$
A2$ → B2$ 變換規則
……
注意:全部字符串長度的上限爲255。
【輸出】
輸出文件名爲word .out。若在30步(包含30步)之內能將A$變換爲B$,則向word .out輸出最少的變換步數;不然向word.out輸出‘ERROR!’。
【樣例輸入】
abcd xyz
abc xu
ud y
y yz
【樣例輸出】
3
【算法分析】
本題是典型的廣度優先搜索的例子,但若是隻採用正向搜索,某些狀況下計算量過大,速度過慢,故採起雙向搜索且判重並適當剪枝,效果較好。
[程序]
program word
const maxn=2300;
type
node=record {定義節點數據類型}
str:string[115];dep:byte;
end; {str表示字串,其長度不會超過115(長度超過115的字串不可能經過變換成爲目標字串,由於題目限定變換10次以內,且串長不超過20,即起始串最多可通過5次變換時增加,中間串的最大長度爲20+5×19=115,不然通過餘下的步數不可能變爲長度不超過20的目標串),dep表示深度}
ctype=array[1..maxn]of ^node;
bin=0..1;
var
maxk:byte;c:array [0..1]of ctype;
x0:array[0..6,0..1]of string[20];
filename:string;
open,closed:array [0..1] of integer;
procedure Init; {讀取數據,初始化}
var f:text;temp:string;i,j:integer;
begin
for i:=0 to 1 do
for j:=1 to maxn do new(c[i,j]);
write('Input filename:');readln(filename);
assign(f,filename);reset(f);i:=0;
while not eof(f) and (i<=6) do begin
readln(f,temp);
x0[i,0]:=copy(temp,1,pos(' ',temp)-1);
x0[i,1]:=copy(temp,pos(' ',temp)+1,length(temp));
inc(i);
end;
maxk:=i-1;close(f);
end;
procedure calc;
var i,j,k:integer;st:bin;
d:string;f:text;
procedure bool(st:bin); {判斷是否到達目標狀態或雙向搜索相遇}
var i:integer;
begin
if x0[0,1-st]=c[st,closed[st]]^.str then begin {若是到達目標狀態,則輸出結果,退出}
writeln(c[st,closed[st]]^.dep);
halt;
end;
for i:=1 to closed[1-st] do
if c[st,closed[st]]^.str=c[1-st,i]^.str then begin {若是雙向搜索相遇(即獲得同一節點),則輸出結果(2個方向搜索的步數之和),退出}
writeln(c[st,closed[st]]^.dep+c[1-st,i]^.dep);
halt;
end;
end;
procedure checkup(st:bin); {判斷節點是否與前面重複}
var i:integer;
begin
for i:=1 to closed[st]-1 do
if c[st,i]^.str=c[st,closed[st]]^.str then begin
dec(closed[st]);exit; {若是節點重複,則刪除本節點}
end;
bool(st); {若是節點不重複,再判斷是否到達目標狀態}
end;
procedure expand(st:bin); {擴展產生新節點}
var i,j,k,lx,ld:integer;
begin
inc(open[st]);d:=c[st,open[st]]^.str;{隊首節點出隊}
k:=c[st,open[st]]^.dep;ld:=length(d);
for i:=1 to maxk do begin {從隊首節點(父節點)出發產生新節點(子節點)}
lx:=length(x0[i,st]);
for j:=1 to ld do begin
if(copy(d,j,lx)=x0[i,st])and(length(copy(d,1,j-1)+x0[i, 1-st]+copy (d,j+lx,ld))<=115)then begin {若是新節點的串長超過115,則不擴展!即剪掉此枝}
if closed[st]>=maxn then exit; {若是隊列已滿,只好退出}
inc(closed[st]); {新節點入隊}
c[st,closed[st]]^.str:=copy(d,1,j-1)+x0[i,1-st]+copy(d,j+lx,ld);
c[st,closed[st]]^.dep:=k+1; {子節點深度=父節點深度+1}
checkup(st); {檢查新節點是否重複}
end;
end;
end;
end;
begin
for st:=0 to 1 do begin {正向(st=0)逆向(st=1)搜索節點隊列初始化}
open[st]:=0;closed[st]:=1;
c[st,closed[st]]^.str:=x0[0,st];c[st,closed[st]]^.dep:=0;
bool(st);
end;
repeat
{選擇節點數較少且隊列未空、未滿、深度未達到10的方向先擴展}
if (open[0]<=open[1]) and not ((open[0]>=closed[0]) or
(closed[0]>=maxn) or (c[0,closed[0]]^.dep>10)) then expand(0);
if (open[1]<=open[0]) and not ((open[1]>=closed[1]) or
(closed[1]>=maxn) or (c[1,closed[1]]^.dep>10)) then expand(1);
{若是一方搜索終止,繼續另外一方的搜索,直到兩個方向都終止}
if not ((open[0]>=closed[0]) or (closed[0]>=maxn) or
(c[0,closed[0]]^.dep>10)) then expand(0);
if not ((open[1]>=closed[1]) or (closed[1]>=maxn) or
(c[1,closed[1]]^.dep>10)) then expand(1);
until(open[0]>=closed[0])or(c[0,closed[0]]^.dep>10)or(closed[0]>=maxn)
and (closed[1]>=maxn) or (open[1]>=closed[1]) or (c[1,closed[1]]^.dep>10);
{終止條件:任一方隊空(無解)或搜索深度超過10(10步內無解)或雙方均溢出(可能有解也可能無解,應儘可能避免,要儘可能把節點數組開大一點,採用雙向搜索,採起剪枝措施等)}
end;
begin
init; calc; writeln('NO ANSWER!')
end .
【點評】
基本題(較難)考察隊列、(雙向)廣度優先搜索算法及字符串的運算,基本上能夠考察出參賽者的數據結構和算法水平。
例2 魔板(magic.pas)。
【題目描述】
在成功地發明了魔方以後,拉比克先生髮明瞭它的二維版本,稱做魔板。這是一張有8個大小相同的格子的魔板,如圖3-18所示。
1 |
2 |
3 |
4 |
8 |
7 |
6 |
5 |
圖3-18 魔板示意圖
咱們知道魔板的每個方格都有一種顏色。這8種顏色用前8個正整數來表示。能夠用顏色的序列來表示一種魔板狀態,規定從魔板的左上角開始,沿順時針方向依次取出整數,構成一個顏色序列。對於圖3-18的魔板狀態,咱們用序列(1,2,3,4,5,6,7,8)來表示。這是基本狀態。
這裏提供3種基本操做,分別用大寫字母A、B、C來表示(能夠經過這些操做改變魔板的狀態)。
A:交換上下兩行;
B:將最右邊的一行插入最左邊;
C:魔板中央做順時針旋轉。
下面是對3種基本狀態進行操做的示範,如圖3-19所示。
(A) (B) (C)
圖3-19 魔板狀態示意圖
對於每種可能的狀態,這3種基本操做均可以使用。
你要編程計算用最少的基本操做完成基本狀態到特殊狀態的轉換,輸出基本操做序列。
【輸入】
只有一行,包括8個整數,用空格分開(這些整數在範圍1~8之間),表示目標狀態。
【輸出】
Line 1: |
包括一個整數,表示最短操做序列的長度 |
Line 2: |
在字典序中最先出現的操做序列,用字符串表示,除最後一行外,每行輸出60個字符 |
【樣例輸入】
2 6 8 4 5 7 3 1
【樣例輸出】
7
B C A B C C B
【算法分析】
雙向廣度優先搜索,先從目標狀態向前搜索6層再進行從初始狀態向目標狀態的搜索,這樣狀態空間大大減小。
【題目描述】
這是一個很古老的遊戲了:有一個3 × 3的活動拼盤(如圖3-20所示),方格上寫有0~8這九個數字。
3 |
7 |
5 |
2 |
6 |
1 |
4 |
8 |
0 |
圖3-20 九數碼遊戲示意圖
利用拼盤背後的旋鈕,遊戲者每次能夠進行兩種操做之一:將拼盤外圍的8個方格按順時針挪一個位置;將中間一行向右移動一個位置,最右邊的方格被移到最左邊,如圖3-21所示。
例如:
圖3-21 九數碼遊戲操做示意圖
給你一個拼盤的初始狀態,你能用最少的操做次數把拼盤變成圖3-22所示的目標狀態嗎?
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
圖3-22 九數碼遊戲目標狀態示意圖
【輸入】
輸入文件中包含3行3列9個數,同行的相鄰兩數用空格隔開,表示初始狀態每一個方格上的數字。初始狀態不會是目標狀態。
【輸出】
若是目標狀態沒法達到,則輸出「UNSOLVABLE」(引號不輸出)。不然,第一行是一個整數S,表示最少的操做次數。接下來4 × (S + 1)行,每四行表示一個狀態:前3行每行3個整數,相鄰兩數用空格隔開,表示每一個方格上的數字,第四行是一個空行,做爲分隔。第一個狀態必須是初始狀態,最後一個狀態必須是目標狀態。
例1 神奇口袋(bag.pas)。
【題目描述】
Pòlya得到了一個奇妙的口袋,上面寫着人類難以理解的符號。Pòlya看得入了迷,左思右想,發現了一個神奇的模型(被後人稱爲Pòlya模型)。爲了生動地講授這個神奇的模型,他帶着學生們作了一個虛擬遊戲。
遊戲開始時,袋中裝入a1個顏色爲1的球,a2個顏色爲2的球,…,at個顏色爲t的球,其中ai∈Z+(1≤i≤t)。遊戲開始後,每次嚴格進行操做:從袋中隨機的抽出一個小球(袋中全部小球被抽中的機率相等),Pòlya獨自觀察這個小球的顏色後將其放回,而後再把d個與其顏色相同的小球放到口袋中。
設ci表示第i次抽出的小球的顏色(1≤ci≤t),一個遊戲過程將會產生一個顏色序列(c1,c2,…,cn, …)。Pòlya 把遊戲開始時t種顏色的小球每一種的個數a1,a2, …,at告訴了全部學生。而後他問學生:一次遊戲過程產生的顏色序列知足下列條件的機率有多大?
其中,0<x1<x2<…<xn,1≤yi≤t。換句話說,已知(t,n,d,a1,a2,…,at, x1,y1,x2,y2,…,xn,yn),你要回答有多大的可能性會發生下面的事件:「對全部k,1≤k≤n,第xk次抽出的球的顏色爲yk」。
【輸入】
第一行有3個正整數t,n,d;第二行有t個正整數a1,a2, …,at,表示遊戲開始時口袋裏t種顏色的球,每種球的個數。
如下n行,每行有兩個正整數xi,yi,表示第xi次抽出顏色爲的yi球。
【輸出】
要求用分數形式輸出(顯然此機率爲有理數)。輸出文件包含一行,格式爲:分子/分母。同時,要求輸出最簡形式(分子分母互質)。特別是機率爲0應輸出0/1,機率爲1應輸出1/1。
【樣例1】
【輸入】
2 3 1
1 1
1 1
2 2
3 1
【輸出】
1/12
【樣例1說明】
初始時,兩種顏色球數分別爲(1, 1),取出色號爲1的球的機率爲1/2;第二次取球以前,兩種顏色球數分別爲(2, 1),取出色號爲2的球的機率爲1/3;第三次取球以前,兩種顏色球數分別爲(2, 2),取出色號爲1的球的機率爲1/2,因此3次取球的總機率爲1/12。
【樣例2】
【輸入】
3 1 2
1 1 1
5 1
【輸出】
1/3
【數據規模和約定】
1≤t,n≤1000, 1≤ak ,d≤10, 1≤x1<x2<…<xn≤10000, 1≤yk≤t
【評分方法】
本題沒有部分分,你的程序的輸出只有和咱們的答案徹底一致才能得到滿分,不然不 得分。
【算法分析】
考察的只是很簡單的數學知識+高精度乘法。整個大框架就是乘法原理,對於每次取球,若是沒有在xi中,那麼此次操做稱爲「自由取球」,不然將顏色yi的球的數量a/此時球的總數做爲乘數乘到結果中,並將顏色yi的球的數量+d,稱做「定向取球」。
能夠證實,自由取球對任意球被取到的概率Pi沒有影響,證實很簡單。能夠忽略掉全部的自由取球操做,對於最後分數化簡的問題,由於分子分母最多隻有20000,因此只要創建一個20000之內的素數表,連高精度除法都不須要,只要先將分子分母都化簡掉再高精度乘起來輸出就好了。
[程序]
type hp = array [0..1100] of longint;
var ca,cb :hp;
j:longint;
t,n,d,i,x,y,sum,npl,pa,pb:longint;
a :array [1..1000] of longint;
prime :array [1..20000] of boolean;
p :array [1..3000] of longint;
xa,xb:array [1..1000] of longint;
procedure mult(var cx:hp; mm:longint);
var i,x:longint;
begin
x:=0;
for i:= 1 to cx[0] do
begin
x:=cx[i]*mm+x;
cx[i]:=x mod 10000;
x:=x div 10000;
end;
while x>0 do
begin
inc(cx[0]);
cx[cx[0]]:= x mod 10000;
x:=x div 10000;
end;
end;
procedure print(var re:hp);
var i,j:longint;
begin
write(re[re[0]]);
for i:= re[0]-1 downto 1 do
if re[i]=0 then write('0000')
else begin
for j:= 1 to 3-trunc(ln(re[i])/ln(10)) do write(0);
write(re[i]); end;
end;
begin
assign(input,'bag.in'); reset(input);
assign(output,'bag.out'); rewrite(output);
fillchar(prime,sizeof(prime),true);
prime[1]:=false;
i:=2;
while i<=20000 do
begin
inc(npl);
p[npl]:=i;
j:=i+i;
while j<=20000 do
begin
prime[j]:=false;
inc(j,i);
end;
inc(i);
while (i<=20000) and not prime[i] do
inc(i);
end;
readln(t,n,d);
for i:= 1 to t do
begin
read(a[i]);
inc(sum,a[i]);
end;
readln;
for i:= 1 to n do
begin
readln(x,y);
xa[i]:=a[y];
xb[i]:=sum;
inc(sum,d);
inc(a[y],d);
end;
for i:= 1 to npl do
begin
pa:=1; pb:=1;
while (pa<=n) and (pb<=n) do
begin
while (pa<=n) and (xa[pa] mod p[i]<>0) do inc(pa);
while (pb<=n) and (xb[pb] mod p[i]<>0) do inc(pb);
if (pa<=n) and (pb<=n) then
begin
xa[pa]:=xa[pa] div p[i];
xb[pb]:=xb[pb] div p[i];
end;
end;
end;
ca[0]:=1;ca[1]:=1;
cb[0]:=1;cb[1]:=1;
for i:= 1 to n do
begin
mult(ca,xa[i]);
mult(cb,xb[i]);
end;
print(ca); write('/'); print(cb);
writeln;
close(input); close(output);
end.
例2 出棧序列統計(stack.pas)。
【題目描述】
棧是經常使用的一種數據結構,有n個元素在棧頂端一側等待進棧,棧頂端另外一側是出棧序列。你已經知道棧的操做有兩種:push和pop,前者是將一個元素進棧,後者是將棧頂元素彈出。如今要使用這兩種操做,由一個操做序列能夠獲得一系列的輸出序列。請你編程求出對於給定的n,計算並輸出由操做數序列1,2,…,n,通過一系列操做可能獲得的輸出序列總數。
【輸入】
就一個數n(1≤n≤1000)。
【輸出】
一個數,便可能輸出序列的總數目。
【樣例輸入】
3
【樣例輸出】
5
【算法分析】
從排列組合的數學知識能夠對此類問題加以解決。咱們先對n個元素在出棧前可能的位置進行分析,它們有n個等待進棧的位置,所有進棧後在棧裏也佔n個位置,也就是說n個元素在出棧前最多可能分佈在2×n位置上。
出棧序列實際上是從這2n個位置上選擇n個位置進行組合,根據組合的原理,從2n個位置選n個,有C(2n,n)個。可是這裏不一樣的是有許多狀況是重複的,每次始終有n個連續的空位置,n個連續的空位置在2n個位置裏有n+1種,因此重複了n+1次。所以出棧序列的種類數目爲(即公式):
ans=c(2n,n)/(n+1)=2n×(2n-1) ×(2n-2)…×(n+1)/n!/(n+1)=2n×(2n-1) ×(2n-2) ×…×(n+2)/n!
考慮到這個數據可能比較大,因此用高精度運算來計算這個結果。
[程序]
program stack;
uses math;
type arr=array[0..5000] of longint;
var n:longint;
i,j:longint;
ans:arr;
function chu(a:arr;b:longint):arr;
var i1,i2,p:longint;
c:arr;
begin
fillchar(c,sizeof(c),0);
p:=a[a[0]];
i1:=a[0];
repeat
if p<b then
begin
p:=p*10;
i1:=i1-1;
p:=p+a[i1];
continue;
end;
c[i1]:=p div b;
p:=p mod b;
p:=p*10;
i1:=i1-1;
p:=p+a[i1];
until i1=0;
for i1:=a[0] downto 1 do
begin
if c[i1]<>0 then
begin
c[0]:=i1;
break;
end;
end;
chu:=c;
end;
function cheng2(a:arr;b:longint):arr;
var i1:longint;
begin
for i1:=1 to a[0] do a[i1]:=a[i1]*b;
for i1:=1 to a[0]+10 do
begin
a[i1+1]:=a[i1+1]+a[i1] div 10;
a[i1]:=a[i1] mod 10;
end;
for i1:=a[0]+10 downto 1 do if a[i1]>0 then
begin
a[0]:=i1;
break;
end;
cheng2:=a;
end;
function cc(a,b:longint):arr;
var i1,i2:longint;
d:arr;
begin
fillchar(d,sizeof(d),0);
d[0]:=1;
d[1]:=1;
for i1:=b downto b-a+1 do
begin
d:=cheng2(d,i1);
end;
for i1:=a downto 1 do d:=chu(d,i1);
cc:=d;
end;
function jia(a,b:arr):arr;
var i1:integer;
begin
for i1:=1 to max(a[0],b[0]) do a[i1]:=a[i1]+b[i1];
for i1:=1 to max(a[0],b[0])+2 do
begin
a[i1+1]:=a[i1+1]+a[i1] div 10;
a[i1]:=a[i1] mod 10;
end;
repeat
i1:=i1-1;
until a[i1]<>0;
a[0]:=i1;
jia:=a;
end;
begin
assign(input,'stack.in');reset(input);
assign(output,'stack.out');rewrite(output);
readln(n);
ans[0]:=1;
ans[1]:=1;
ans:=chu(cc(n,2*n),n+1);
for i:=ans[0] downto 1 do write(ans[i]);
close(input);
close(output);
end.
例3 稱硬幣(coin.pas)。
【題目描述】
如今,有從1~N編了號的N塊硬幣,其中有一塊是假的,重量與真硬幣不一樣。全部的真硬幣的重量都同樣。如今,已經用天平稱了K次,要你求出哪一塊硬幣是假的。
【輸入】
第一行有兩個整數N和K(1≤N,K≤100)。接下來的2K行描述已經稱了的硬幣的狀況,每一個狀況用兩行表示:第一行有一個整數Pi,(1≤Pi≤N/2),表示放在天平每一個盤子中的硬幣的個數,接下來的Pi個整數表示左盤中的硬幣的編號,再接下來的Pi個整數表示右盤中的硬幣的編號。第二行有「<」、「>」、「=」字符,表示此次稱量的結果。「<」表示左盤中的硬幣比右盤中的硬幣輕;「>」表示左盤中的硬幣比右盤中的硬幣重;「=」表示左盤中的硬幣與右盤中的硬幣重量相等。
【輸出】
若是能夠肯定哪一塊是假幣,則輸出假幣的編號,不然輸出0。
【樣例輸入】
5 3
2 1 2 3 4
<
1 1 4
=
1 2 5
=
【樣例輸出】
3
【算法分析】
對3種狀況分類討論:(1)若是是不等號則證實剩下的不是假幣,假幣在不等號裏面產生;(2)若是是等號則證實剩下的有假幣,等號兩側均不是假幣;(3)而假幣只有一個。
[程序]
program coin;
var b:array[1..100] of 0..2;
a:array[1..100,-1..100] of integer;
n,k:longint;
procedure init;
var f:text;
i,j:integer;
ch:char;
begin
fillchar(b,sizeof(b),0);
assign(f,'coin.in');
reset(f);
readln(f,n,k);
for i:=1 to k do
begin
read(f,a[i,0]);
for j:=1 to 2*a[i,0] do read(f,a[i,j]);
readln(f);
readln(f,ch);
case ch of
'=': begin
for j:=1 to 2*a[i,0] do b[a[i,j]]:=1;
a[i,-1]:=0;
end;
'<': begin
for j:=1 to 2*a[i,0] do if b[a[i,j]]=0 then b[a[i,j]]:=2;
a[i,-1]:=1;
end;
'>': begin
for j:=1 to 2*a[i,0] do if b[a[i,j]]=0 then b[a[i,j]]:=2;
a[i,-1]:=2;
end;
end;
end;
end;
procedure print(ans:integer);
var f:text;
begin
assign(f,'coin.out');
rewrite(f);
writeln(f,ans);
close(f);
end;
function check(x:integer):boolean;
var i,j,t,px:integer;
p:boolean;
begin
px:=0;
for i:=1 to k do
case a[i,-1] of
0: for j:=1 to 2*a[i,0] do
if a[i,j]=x then begin check:=false;exit;end;
1: begin
t:=0;
for j:=1 to 2*a[i,0] do
if a[i,j]=x then t:=j;
if t=0 then begin check:=false;exit;end;
if t<=a[i,0] then
begin
if px=0 then px:=-1;
if px=1 then begin check:=false;exit;end;
end;
if t>a[i,0] then
begin
if px=0 then px:=1;
if px=-1 then begin check:=false;exit;end;
end;
end;
2: begin
t:=0;
for j:=1 to 2*a[i,0] do
if a[i,j]=x then t:=j;
if t=0 then begin check:=false;exit;end;
if t<=a[i,0] then
begin
if px=0 then px:=1;
if px=-1 then begin check:=false;exit;end;
end;
if t>a[i,0] then
begin
if px=0 then px:=-1;
if px=1 then begin check:=false;exit;end;
end;
end;
end;
check:=true;
end;
procedure main;
var i,j,k,t,ans:integer;
begin
t:=0;ans:=0;
for i:=1 to n do if b[i]=1 then inc(t);
if t=n-1 then
for i:=1 to n do if b[i]<>1 then
begin print(i);halt;end;
if t=n then begin print(0);halt;end;
for i:=1 to n do
begin
if b[i]=1 then continue;
if check(i) then
if ans<>0 then begin print(0);halt;end
else ans:=i;
end;
print(ans);
end;
begin
init;
main;
end.
例4 均分紙牌(a.pas)。
【題目描述】
有N堆紙牌,編號分別爲1,2,…N。每堆上有若干張, 但紙牌總數必爲N的倍數。能夠在任一堆上取若干張紙牌,而後移動。移牌規則爲:在編號爲1堆上取的紙牌,只能移到編號爲2的堆上;在編號爲N的堆上取的紙牌,只能移到編號爲N-1的堆上;其餘堆上取的紙牌,能夠移到相鄰左邊或右邊的堆上。如今要求找出一種移動方法,用最少的移動次數使每堆上紙牌數都同樣多。例如,N=4,4堆紙牌數分別爲:①9;②8;③17;④6。移動3次可達到目的:從③取4張牌放到④(9 8 13 10)→從③取3張牌放到②(9 11 10 10)→從②取1張牌放到①(10 10 10 10)。
【輸入】 N ( N 堆紙牌,1≤N≤100);
A1,A2,…An(N堆紙牌,每堆紙牌初始數,1≤Ai≤10 000)。
【輸出】全部堆均達到相等時的最少移動次數。
【樣例輸入】
4
9 8 17 6
【樣例輸出】
3
【算法分析】
本題有3點比較關鍵:一是善於將每堆牌數減去平均數,簡化了問題;二是要過濾掉0(不是全部的0,如-2,3,0,-1中的0是不能過濾的);三是負數張牌也能夠移動,這是辯證法(關鍵中的關鍵)。
[程序]
program a;
var num:array[0..100]of integer;
sum:longint;
time,i,n:integer;
begin
assign(input,'a.in');reset(input);
assign(output,'a.out');rewrite(output);
fillchar(num,sizeof(num),0);
readln(n);
sum:=0;
time:=0;
for i:=1 to n-1 do
begin
read(num[i]);
inc(sum,num[i]);
end;
readln(num[n]);
inc(sum,num[n]);
sum:=sum div n;
for i:=1 to n do
if num[i]<>sum then begin
inc(time);
inc(num[i+1],num[i]-sum);
num[i]:=sum;
end;
writeln(time);
close(input);
close(output);
end.
例5 矩陣(bincod.pas)。
【題目描述】
考慮一個N位的二進制串b1…bN,其對應的二進制矩陣爲:
而後將這個矩陣的行按照字典順序排序,「0」在「1」以前。
如今,你的任務是編寫一個程序,給定排序後的矩陣的最後一列,求出已排序矩陣的第一行。
例如:考慮二進制串(00110),排序後的矩陣爲:
則該矩陣的最後一列爲(1 0 0 1 0)。給出了這一列,你的程序應該肯定第一行爲 (0 0 0 1 1)。
【輸入】
輸入文件名爲bincod.in。第一行包含一個整數N(0≤20000),表示二進制串的位數。第二行包含N個整數,表示已排序的矩陣的最後一列(從上到下)。
【輸出】
輸出文件名爲bincod.out。輸出文件僅一行,包含N個整數,從左到右依次表示已排序的矩陣的第一行;若無解,則輸出-1。
【樣例輸入】
5
1 0 0 1 0
【樣例輸出】
0 0 0 1 1
【算法分析】
題目給出方陣末列的字串,根據題意能夠知道:將末列按照從0~1進行排序,便可獲得方陣首列的字串。又由於全部的字串都是由0和1組成,因此排序的過程能夠簡略爲統計0和1的數量便可。對於樣例,該過程以後,首列和末列的字串以下所示:
首列 0 0 0 1 1
末列 1 0 0 1 0
然而此時末列和首列各個字符之間的對應關係仍就難以很直觀地體現。字符之間的前後順序難以肯定。那麼就讓咱們換一種思路,將每一個字符所對應位置替換上面所示的字符,咱們獲得:
首列 2 3 5 1 4
末列 1 2 3 4 5
這樣,末列與首列的對應關係便清晰可見了。按照上面所示的順序,能夠將方陣的第一行的字符位置串接起來:1→4→5→3→2,再將每一個位置換成所對應的字符,即獲得第一行的字串。
關於無解的數據,只須要統計所得字串中0和1的數量是否與給定字串的相同便可。
[程序]
program bincod;
var a,b,c,cc,bn,nz,no:array [1..20000] of integer;
i,n,z,o,nzp,nop,d,k,p:longint;
first,max,sum,maxposition,cz,ci:longint;
begin
assign(input,'bincod.in'); reset(input);
assign(output,'bincod.out');rewrite(output);
readln(n); z:=0; o:=0;{count01數目}
nzp:=0; nop:=0; {臨時01指針}
for i:= 1 to n do begin read(a[i]);
if a[i]=0 then begin inc(z); inc(nzp); nz[nzp]:=i; end;
if a[i]=1 then begin inc(o); inc(nop); no[nop]:=i; end;
end;
nzp:=0; nop:=0;
for i:= 1 to z do begin b[i]:=0;inc(nzp);bn[i]:=nz[nzp];end;
for i:= 1+z to o+z do begin b[i]:=1;inc(nop);bn[i]:=no[nop];end;
k:=0; p:=1; d:=1;
repeat
inc(k); c[k]:=a[p]; p:=bn[p];
until k=n;
first:=0; sum:=0; max:=0; maxposition:=0; k:=1;
repeat
if c[k]=0 then inc(sum) else begin
if sum>max then begin max:=sum;
maxposition:=k; end;
sum:=0;
end;
inc(k);
until k>n;
k:=1;
if (c[1]=0) and (c[n]=0) then
repeat
if c[k]=0 then inc(sum) else begin
if sum>max then begin max:=sum;
maxposition:=k;
end;
sum:=0;
end;
inc(k);
until k>n;
for i:= 1 to n do begin
if c[i]=0 then begin inc(cz); end;
end;
if cz<>z then begin writeln(-1); close(output); close(input); halt; end;
fillchar(cc,sizeof(cc),0); ci:=0;
if maxposition-max>0 then
begin
for i:= maxposition-max to n do begin inc(ci); cc[ci]:=c[i] end;
for i:= 1 to maxposition-max-1 do begin inc(ci); cc[ci]:=c[i] end;
end else begin
for i:= 1 to max do begin inc(ci); cc[ci]:=0 end;
for i:= maxposition to n-(max-maxposition+1) do begin inc(ci); cc[ci]:=c[i] end;
end;
for i:= 1 to n do begin write(cc[i]); write(' '); end; writeln;
close(input);close(output);
end.
小結:對於此類沒有現成算法能夠套用的題目,而搜索算法又明顯低效時,對於選手的思惟能力和基本功都是一個很好的鍛鍊。只有在紮實的基礎之上,纔會有這種「靈光一閃」的靈感,可謂是:「山窮水復疑無路,柳暗花明又一村。」
【題目描述】
今年是國際數學聯盟肯定的「2000——世界數學年」,又恰逢我國著名數學家華羅庚先生誕辰90週年。在華羅庚先生的家鄉——江蘇金壇,組織了一場別開生面的數學智力競賽的活動,你的一個好朋友XZ也有幸得以參加。活動中,主持人給全部參加活動的選手出了這樣一道題目:設有一個長度N的數字串,要求選手使用K個乘號將它分紅K+1個部分,找出一種分法,使得這K+1個部分的乘積可以爲最大。同時,爲了幫助選手可以正確理解題意,主持人還舉了一個例子:
有一個數字串:312,當N=3,K=1時會有如下兩種分法:
(1)3×12=36
(2)31×2=62
這時,符合題目要求的結果是:31×2=62。
如今,請幫助你的好朋友XZ設計一個程序,求得正確的答案。
【輸入】
程序的輸入共有兩行:第一行共有2個天然數N,K (6≤N≤40,1≤K≤6);第二行是一個K度爲N的數字串。
【輸出】
結果顯示在屏幕上,相對於輸入,應輸出所求得的最大乘積(一個天然數)。
【樣例輸入】
4 2
1231
【樣例輸出】
62
設有1G、2G、3G、5G、10G、20G的砝碼各若干枚(其總重≤1000G)。
【輸入】
A1 A2 A3 A4 A5 A6
(表示1G砝碼有A1個,2G砝碼有A2個…20G砝碼有A6個)
【輸出】
Total=N
(N表示這些砝碼能稱出的不一樣重量的個數,但不包括一個砝碼也不用的狀況)
【樣例輸入】
1 1 0 0 0 0
【樣例輸出】
Total=3(表示能夠稱出1g、2g、3g 3種不一樣的重量)
以上介紹的是一些經常使用的搜索算法,這些算法是經典的和有效的,能夠解決至關一部分的問題。可是,不少問題會有比較複雜的狀況和比較特殊的約束條件,那麼可能以上的算法將不能直接使用解決。因此,咱們應該根據問題的實際狀況,對原有的搜索算法作相應的修改,優化原有的搜索情況,使之更有效地完成問題的要求。同時,優化也是許多搜索問題比較注重的一個方面,不少問題,可能考的就是搜索的優化,而不是搜索的方法。
下面,列舉出一些在實際問題中比較經常使用的優化方法。
(1)縮小問題的狀態空間。若是在搜索開始以前,能夠縮小問題的狀態空間,減小狀態結點數,或者下降問題的規模,那麼將會大大地減少搜索量,提升搜索的效率。
(2)在廣度優先搜索算法中,擴展存儲結點的內存空間。廣度優先搜索算法的一個重要問題是佔用存儲空間比較大,每每在尚未找到問題的最優解以前,已經沒有空間存放新擴展出來的結點了,從而致使搜索失敗。咱們能夠利用一些循環數組,鏈表等數據結構,充分釋放一些無用結點的存儲空間,以騰出空間存儲更多的新結點。
(3)根據問題的約束條件,在搜索中剪枝。在搜索的過程當中,若是能夠判斷出一個結點,它不可能擴展到最優解,咱們就能夠刪除這個結點,這個結點的全部子結點將不被擴展出來。這是極其重要和經常使用的一個搜索優化方法。
(4)利用搜索過程當中的中間解。爲了提升搜索效率,咱們能夠適當地將搜索過程當中的一些中間解存儲下來,之後遇到相同的結點,將不用再一次進行搜索,而直接使用存儲下來的數據。
(5)進行解變換。當找到一個解(可是並非最優解)以後,咱們能夠不用急着在狀態空間中改變搜索方向,去尋找另一個解。而能夠嘗試經過改變已有解的結構,看看是否能夠經過已經找到的解,變換到咱們須要求的最優解。
(6)尋找問題的隱性條件。有些問題存在着一些隱藏的條件,若是能夠發現這些條件,將能夠大大約束結點的擴展數量,以致儘快找到問題的解。
(7)問題分治。有些問題比較複雜和龐大,直接搜索可能搜索量比較大,搜索效率相應比較低。那麼,能夠先將問題分紅一些子問題,先搜索到這些子問題的解,再經過一些合併和推導的方式,找到原有問題的解。
其實,全部的優化方法都只有一個目的,就是減小搜索量,提升搜索效率,儘量快地找到問題的解。但願你們在使用搜索方法的過程當中,多分析問題,找到有效的優化方法,提升使用搜索技術的能力。
例1 可否組成一個正方形。
【題目描述】
給出M個數,判斷這些數可否組成一個正方形。
【算法分析】
能夠採用分步搜索,先判斷可不能夠分紅兩組和相同的數,再分別判斷這組是否可再分爲和相等的兩組。分紅兩組和相同的數,也就是搜索一組和爲總和一半的數,若是成功,則能分紅兩組。
【優化】
兩個經典剪枝:
(1)當前的和大於目標和,剪掉。
(2)當前和加i之後的全部數之和小於目標和,剪掉(預處理時,能夠對數先進行排序,再求b[i](數i以及i之後的數的和))細節分析:
對數組進行第一步分組,產生兩組數,一組標記爲0,另一組標記爲2,成功分組後再分別對這兩組進行分組,0的可再分爲0、一、2組可再分爲2、3,須要注意的是當0組分配成功,2組分組未成功時,須要回溯到先一次遞歸,這時標記數組原來的0可能爲0和1、2可能爲2或是3,因此在之後寫程序時要注意這一點。
例2 加括號。
【題目描述】
輸入兩個數字(I,j),在第一個數字i中間添上若干個加號,使得這個代數式的值等於第二個數字j。
【算法分析】
這道題採用的算法是剪枝搜索,逐一在第一個數字i各個數字之間添上加號,並計算出當前代數式的值,若是等於第二個數字j,則退出循環,打印結果「YES」;若是不是,則繼續循環;當循環結束後,還未能得出第二個數j,則打印結果「NO」。
【算法流程】
(1)首先將以字符串輸入的第一個數字i切成一個數字e段,放進一個數組a[e]裏。
(2)接着進行循環,逐一在第一個數字i各個數字之間添上加號,因爲在各個數字之間添上加號後代數式的值只有兩種狀況:大於第二個數字j或小於第二個數字j,因此循環的內容以下所示。
①以h,i分別做爲當前處理的數字的首位在數組裏的位置和在當前數字的第i位添 加號。
②分別以x1,x2存放添加號後加號前的數值以及加號後的數值,x3將賦值爲x1+x2,做爲當前代數式的值。當x3>j時,則進行將h賦值爲i,將要處理的數字賦值爲加號後的數值,並將x3賦值爲x3-x2,做爲加號前的數值;當x3<j,並且i≤e時,則將i賦值爲i+1,在當前處理的數字添加號的數位的後一位添上加號,並將x3賦值爲x3-x2;若i>e時,則將h賦值爲h-1,i賦值爲h+1,在當前處理的數字添加號的數位的前一位添上加號,並將x3賦值爲x3-x2-a[i]。
(3)當h≤0、h>e、i≤0、i>e、x3<>j時,則退出循環並打印結果「NO」;當0<h≤e、0<i≤e、x3=j時,則退出循環並打印結果「YES」。
[程序]
program aa;
var a,b:text;
c:string;
d,e,g,h,i,x1,x2,x3,xx:integer;
f:array [1..100] of integer;
procedure bb;
begin
assign(a,'tjh.dat');
reset(a);
readln(a,c,d);
close(a);
end;
procedure cc;
begin
for e:=1 to length(c) do
f[e]:=ord(c[e])-48;
x1:=0;
x2:=0;
x3:=0;
xx:=1;
h:=1;
i:=1;
while x3<>d do
begin
for g:=h to i do x1:=x1*10+f[g];
for g:=i+1 to e do x2:=x2*10+f[g];
x3:=x1+x2+x3;
if x3=d then exit;
if (h<0) or (i<0) then begin xx:=0; exit; end;
if x3<d then
begin
i:=i+1;
x3:=x3-x1-x2;
if i=5 then begin h:=h-1; i:=h+1; x3:=x3-f[i-1]; end;
x1:=0;
x2:=0;
end;
if x3>d then
begin
h:=h+1;
i:=h;
x1:=0;
x3:=x3-x2;
x2:=0;
end;
end;
end;
procedure dd;
begin
assign(b,'tjh.out');
rewrite(b);
if xx=1 then writeln(b,'yes')
else writeln(b,'no');
close(b);
end;
begin
bb;
cc;
dd;
end.
例3 蟲食算(alpha.pas)。
【題目描述】
所謂蟲食算,就是原先的算式中有一部分被蟲子啃掉了,須要咱們根據剩下的數字來斷定被啃掉的字母。來看一個簡單的例子:
43#9865#045
+ 8468#6633
其中#號表明被蟲子啃掉的數字。根據算式,咱們很容易判斷:第一行的兩個數字分別是5和3,第二行的數字是5。
如今,咱們對問題作兩個限制:首先,咱們只考慮加法的蟲食算。這裏的加法是N進制加法,算式中3個數都有N位,容許有前導的0。其次,蟲子把全部的數都啃光了,咱們只知道哪些數字是相同的,咱們將相同的數字用相同的字母表示,不一樣的數字用不一樣的字母表示。若是這個算式是N進制的,咱們就取英文字母表午的前N個大寫字母來表示這個算式中的0~N-1這N個不一樣的數字:可是這N個字母並不必定順序地表明0~N-1。輸入數據保證N個字母分別至少出現一次。
BADC
+ CRDA
DCCC
上面的算式是一個4進制的算式。很顯然,咱們只要讓ABCD分別表明0123,即可以讓這個式子成立了。如今,你的任務是對於給定的N進制加法算式,求出N個不一樣的字母分別表明的數字,使得該加法算式成立。輸入數據保證有且僅有一組解。
【輸入】
輸入文件alpha.in包含4行。第一行有一個正整數N(N≤26),後面的3行每行有一個由大寫字母組成的字符串,分別表明兩個加數以及和。這3個字符串左右兩端都沒有空格,從高位到低位,而且剛好有N位。
【輸出】
輸出文件alpha.out包含一行。在這一行中,應當包含惟一的那組解。解是這樣表示的:輸出N個數字,分別表示A,B,C…所表明的數字,相鄰的兩個數字用一個空格隔開,不能有多餘的空格。
【樣例輸入】
5
ABCED
BDACE
EBBAA
【樣例輸出】
1 0 3 4 2
【數據規模】
對於30%的數據,保證有N≤10;
對於50%的數據,保證有N≤15;
對於所有的數據,保證有N≤26。
【算法分析】
經典的搜索題。最單純的搜索的時間複雜度爲O(n!),是會嚴重超時的。計算機是很「笨」的,它不會思考,在盲目搜索的過程當中,很容易出現這種狀況:計算機在某一位搜索出了一個算式1 + 1 = 3,而且繼續搜索。
明顯,人眼很容易就看出這是不合法的,但計算機不會。因而,咱們想到了第一個剪枝:每次搜索的時候,從最後向前判斷是否有不合法的式子。這一個剪枝很是簡單,並且效果也很是的好。由於它剪去了不少沒必要要的搜索。爲了配合這一種剪枝更好的實行,搜索順序的改變也成爲大大提升程序效率的關鍵:從右往左,按照字母出現順序搜索,在很大程度上提升了先剪掉廢枝的狀況,使程序的效率獲得大大提升。有了以上兩個剪枝,程序就已經能夠經過大部分測試點了。可是有沒有更多的剪枝呢?答案是確定的。
根據前面的剪枝,咱們能夠找到相似的幾個剪枝:對於A+B=C的形式,假如:
A***?***
+ B*?**?**
C***???*
其中:*表明已知,?表明未知。那麼,A + B與C的狀況並不能直接肯定。可是,假如(A + B)% N與(A + B + 1)% N都不等於C的話,那麼這個等式必定是不合法的。由於它只有進位和不進位的兩種狀況。
一樣,咱們在一個數組裏記錄了Used[i]表示一個數字有沒有用過,那麼,對於某一位 A + B = C的等式,若是已經獲得了兩個數,另外一個數還待搜索的時候,咱們還能夠根據這個加入一個剪枝:②對於A + ? = C的形式,假如:
考慮不進位的狀況,則?處爲P1 = (C - A + N) % N;
考慮進位的狀況,則?處爲P2 = (C - A - 1 + N) % N;
P1、P2均被使用過,那麼這個搜索必定是無效的,能夠剪去。
有了以上的剪枝,就能夠很輕鬆地經過全部的測試數據了。固然,還有不少值得思考的剪枝以及其餘的思路。例如,枚舉進位、解方程(可是可能須要枚舉)等,在這裏就不詳細討論了。
小結:搜索題的框架每每不難找到,關鍵就是在搜索的優化上。搜索問題的優化更多的須要選手的經驗和思考及分析問題的能力,因此搜索剪枝也是競賽中經久不衰的經典問題。
多階段決策的最優化問題:所謂多階段決策問題是指這樣一類問題,該問題的決策過程能夠按時間順序分解成若干相互聯繫的階段,在每個階段分別作出決策從而造成決策序列,使得整個活動的整體效果達到最優。能夠看出,這類問題有兩個要素:一個是階段,一個是決策。
階段:將所給問題的過程,按時間或空間等天然特徵分解成若干個相互聯繫的階段,以便按次序去求每階段的解。描述階段的變量稱爲階段變量,經常使用字母k表示階段變量。階段是問題的屬性。從階段的定義中能夠看出階段的兩個特色,一是「相互聯繫」,二是「次序」。
狀態:各階段開始時的客觀條件叫作狀態。描述各階段狀態的變量稱爲狀態變量,經常使用sk表示第k階段的狀態變量,狀態變量sk的取值集合稱爲狀態集合,用Sk表示。狀態是階段的屬性。每一個階段一般包含若干個狀態,用以描述過程發展到這個階段時所處在的一種客觀狀況。
這裏所說的狀態應該具備下面的性質:將各階段按照必定的次序排列好以後,對於某個給定的階段狀態,它的決策只依賴於當前的狀態,而與之前各階段的狀態無關。換句話說:過去的歷史只能經過當前的狀態去影響它將來的發展,當前的狀態是對以往歷史的一個總結。這個性質稱爲無後效性。
決策:當各階段的狀態取定以後,就能夠作出不一樣的決定,從而肯定下一階段的狀態,這種決定稱爲決策。表示決策的變量,稱爲決策變量,經常使用uk(sk)表示第k階段當狀態爲sk時的決策變量。在實際問題中,決策變量的取值每每限制在必定範圍內,咱們稱此範圍爲容許決策集合。經常使用Dk(sk)表示第k階段從狀態sk出發的容許決策集合。顯然有uk(sk)ÎDk(sk)。決策是問題的解的屬性。決策的目的就是「肯定下一階段的狀態」。
狀態轉移:動態規劃中本階段的狀態每每是上一階段的狀態和上一階段的決策的結果,由第k段的狀態sk和本階段的決策uk肯定第k+1段的狀態sk+1的過程叫狀態轉移。狀態轉移規律的形式化表示sk+1=Tk(sk,uk)稱爲狀態轉移方程。
因而可知,狀態轉移是決策的目的,決策是狀態轉移的途徑。決策和狀態轉移是導出狀態的途徑,也是聯繫各階段的途徑。
策略:各階段的決策肯定後,整個問題的決策序列就構成了一個策略,用p1,n={u1(s1),u2(s2),…,un(sn)}表示。對每一個實際問題,可供選擇的策略有必定範圍,稱爲容許策略集合,記做P1,n,使整個問題達到最優效果的策略就是最優策略。
最優化原理:整個過程的最優策略應具備這樣的性質:不管初始狀態及初始決策如何,對於先前決策所造成的狀態而言,其之後的全部決策必須構成最優策略。簡言之,就是「最優策略的子策略也是最優策略」。在動態規劃的算法設計中,這一性質又被稱做最優子結構性質。
最優指標函數:用於衡量所選定策略優劣的數量指標稱爲指標函數,最優指標函數記爲fk(sk),它表示從第k段狀態sk採用最優策略p×k,n到過程終止時的最佳效益值。最優指標函數其實就是咱們真正關心的問題的解。最優指標函數的求法通常是一個從目標狀態出發的遞推公式,稱爲規劃方程。
其中sk是第k段的某個狀態,uk是從sk出發的容許決策集合Dk(sk)中的一個決策,Tk(sk,uk)是由sk和uk所導出的第k+1段的某個狀態sk+1,g(x,uk)是定義在數值x和決策uk上的一個函數,而函數opt表示最優化,根據具體問題分別表爲max或min。某個初始值,稱爲邊界條件。
這裏是一種從目標狀態往回推的逆序求法,適用於目標狀態肯定的問題。固然,對於初始狀態肯定的問題,咱們也能夠採用從初始狀態出發往前推的順序求法。事實上,這種方法對咱們來講要更爲直觀、更易設計一些,從而更多地出如今咱們的解題過程當中。
在動態規劃的算法設計中,最優指標函數與狀態轉移方程統稱爲狀態轉移方程,它是動態規劃模型的核心,也是動態規劃算法設計的關鍵。
例1 「破鑼搖滾」樂隊(g2.pas)。
你剛剛繼承了流行的「破鑼搖滾」樂隊錄製的還沒有發表的N(1≤N≤600)首歌的版權。你打算從中精選一些歌曲,發行M(1≤M≤600)張CD。每一張CD最多能夠容納T(1≤T≤10000)分鐘的音樂,一首歌不能分裝在兩張CD中。不巧你是一位古典音樂迷,不懂如何斷定這些歌的藝術價值。因而你決定根據如下標準進行選擇:(1)歌曲必須按照創做的時間順序在CD盤上出現;(2)選中的歌曲數目儘量地多。
【輸入】
多組數據。第一行三個整數:N、T、M。第二行N個整數,分別表示每首歌的長度,按創做時間順序排列。
【輸出】
一個整數,表示能夠裝進M張CD盤的樂曲的最大數目。本題有多組測試數據。
【樣例輸入】
4 5 2
4 3 4 2
【樣例輸出】
3
【算法分析】
直接說最優作法,O(n^2),記f[I,j]爲前i個曲子取j個最小時間,則 f[I,j]:=min(f[i-1,j],sum(f[i-1,j-1],g[I,j])),注意這個設法很巧妙。另外注意陷阱!有的碟片不能放進去。
[程序]
grogram g2
Uses Math;
Const L=1000;
INF=100000;
Var g: array [0..L] of longint;
f: array [0..L,0..L] of longint;
n,t,m,i,j,ans: longint;
Function Sum(a,b:longint):longint;
var c,d:longint;
begin
d:= a div t + 1;
c:= t - a mod t;
if c >= b then exit( a + b )
else exit( d * t + b );
end;
begin
assign(input,'g2.in'); reset(input);
assign(output,'g2.out'); rewrite(output);
repeat
readln(n,t,m);
if n=0 then break;
i:=0; j:=0;
while j<n do
begin
inc(j); inc(i);
read(g[i]);
if g[i] > t then dec(i);
end; readln;
n:=i;
fillchar(f,sizeof(f),0);
for i:= 0 to n do
for j:= 1 to n do
f[i,j]:=inf;
for i:= 1 to n do
for j:= 1 to i do
f[i,j]:=min(f[i-1,j],sum(f[i-1,j-1],g[i]));
ans:=0;
for i:= n downto 1 do
if f[n,i] <= t * m then
begin
ans:=i;
break;
end;
writeln(ans);
until eof;
close(input); close(output);
end.
例2 過河卒(river.pas)。
【題目描述】
如圖3-23所示,A點有一過河卒,須要走到目標B點。卒行走的規則:能夠向下,或者向右。
圖3-23 過河卒示意圖
同時在棋盤上的任一點有一個對方的馬(如圖3-22中C點),該馬所在的點和全部跳躍一步可達的點稱爲對方馬的控制點。
例如,圖3-22中C點的馬可控制9個點(P1…P8,C)。卒不能經過對方馬的控制點。棋盤用座標表示,A點(0,0),B點(n,m)(n,m爲不超過20的整數,並有鍵盤輸入),一樣,馬的位置座標是須要給出的(約定:C≠A同時C≠B)。
如今,須要你計算出卒從A點出發可以到達B點的路徑的條數。
【輸入】
B點座標(n,m)以及對馬的座標(X,Y)。{不用判錯}
【輸出】
一個整數。(路徑的條數)
【樣例輸入】
6 6 3 2
【樣例輸出】
17
【算法分析】
先開一個[-2..22,-2..22]的數組,以保證不出現錯誤201(數組越界)。讀入馬的位置,(a,b)末位置。數組清零。將馬控制的位置變成-1。下面計算:
for i:=a downto 0 do
for j:=b downto 0 do
若是那兩個沒有-1,就加;有-1就不加-1;都是-1就便0。輸出(0,0)。
[程序]
program river;
var a,b,c,d,e,f,g,h,i,j,k,l:longint;pp:boolean;
x:array[-2..22,-2..22] of int64;
procedure init();
begin
read(a,b,c,d);
end;
procedure befor();
begin
fillchar(x,sizeof(x),0);
x[c,d]:=-1;
x[c-1,d-2]:=-1;
x[c-2,d-1]:=-1;
x[c-1,d+2]:=-1;
x[c-2,d+1]:=-1;
x[c+1,d+2]:=-1;
x[c+1,d-2]:=-1;
x[c+2,d+1]:=-1;
x[c+2,d-1]:=-1;
end;
procedure doit();
begin
x[a,b+1]:=1;
for i:=a downto 0 do
for j:=b downto 0 do
if (x[i,j]<>-1) then
begin
if (x[i,j+1]=-1) and (x[i+1,j]=-1) then begin x[i,j]:=0; pp:=false; end;
if (x[i,j+1]<>-1) and (x[i+1,j]=-1) and pp then x[i,j]:=x[i,j+1];
if (x[i,j+1]=-1) and (x[i+1,j]<>-1) and pp then x[i,j]:=x[i+1,j]; pp:=true;
if (x[i,j+1]<>-1) and (x[i+1,j]<>-1) then x[i,j]:=x[i,j+1]+x[i+1,j];
end;
write(x[0,0]);
end;
begin
assign(input,'river.in');reset(input);
assign(output,'river.out');rewrite(output);
pp:=true;
init();
befor();
doit();
close(input);close(output);
end.
例3 數的計算-增強版(cd1.pas)。
Problem
先輸入一個天然數n(n≤3000000),而後對此天然數按照以下方法進行處理:
(1)不做任何處理。
(2)在它的左邊加上一個天然數,但該天然數不能超過原數的一半。
(3)加上數後,繼續按此規則進行處理,直到不能再加天然數爲止。
例如,n=6。
6;16;26;126;36;136。因此知足要求的個數爲6。
【輸入】
包含多個測試數據,每行是一個整數n。(1≤n≤3000000)
【輸出】
一個整數,表示解的個數。(保證不超過50位)
【樣例輸入】
6
【樣例輸出】
6
【算法分析】
原題是找規律。
1→1 2→2 3→2 4→4 5→4 6→6 7→6 8→10 9→10 10→14 11→14 12→20 13→20
so... f[n]:=f[1]+f[2]+...f[n div 2] <=> n奇數 f[n-1]
n偶數 f[n-1]+f[n div 2]
結果很大,高精度是固然的。
[程序]
program tju1059;
const half=1500000;
var
s:array[0..half,0..2]of int64;
n,i:longint;
base:int64;
m:integer;
procedure tail(a:int64);
var
s:string;
i:byte;
begin
str(a,s);
for i:=length(s) to 17 do write('0');
write(s);
end;
procedure out(a,b,c:int64);
begin
if a>0 then begin
write(a);tail(b);tail(c);
end
else if b>0 then begin
write(b);tail(c);
end
else
write(c);
writeln;
end;
begin
assign(input,'cd1.in');reset(input);
assign(output,'cd1.out');rewrite(output);
base:=1;
for m:=1 to 9 do base:=base*10; {10的9次方,在fp2.0下,只能用循環來實現}
base:=base*base;
s[0,0]:=1;
for n:=1 to half do
for i:=0 to 2 do begin
inc(s[n,i],s[n-1,i]+s[n shr 1,i]);
if s[n,i]>=base then begin
inc(s[n,i+1]);dec(s[n,i],base);
end;
end;
repeat
read(n);n:=n shr 1;
out(s[n,2],s[n,1],s[n,0]);
until seekeof;
close(input);close(output);
end.
例4 文科生的悲哀(d3.pas)。
【題目描述】
化學不及格的Matrix67無奈選擇了文科。他必須硬着頭皮艱難地進行着文科的學習。
這學期的政治、歷史和地理課本各有n章。每一科的教學必須按章節從前日後依次進行。若干章政治、若干章歷史和若干章的地理內容能夠合成一個教學階段。年級計劃將整個學期的內容分紅若干個階段進行教學。爲了保證各科教學進度相同,年級規定每個階段包含的各科的章節數必須相同。一個階段包含的章節越多,這個階段所須要的課時也就越多。通過研究,假如某個階段包含政、史、地各k章,則政治學習須要花費3k天的課時,歷史學習須要花費5k天的課時,地理學習須要花費2k天的課時,最後還須要4天的綜合訓練。一個階段所花費的總時間是以上四項時間的和。
爲了便於安排時間,學校但願每一個階段剛好須要若干周來完成。所以,劃分出的每個階段所須要的天數都必須是7的整數倍(高三是沒有星期六和星期天的)。
那麼,這學期的課程最多能夠劃分紅多少個階段呢?你會想到,要想劃分的階段數最多,一個階段完成一章的任務就好了(由於31+51+21+4=14是7的整數倍)。但問題沒有這麼簡單。每一個課本均可能有一些獨立性較強的連續章節,它們具備很強的連續性,必須在一個階段中完成。若是你已知全部不能劃分在兩個或兩個以上的階段中的連續章節,你還能計算出最多能安排多少個階段嗎?
【輸入】
第一行有兩個用空格隔開的正整數n和m,分別表示各科課本的章節數和不可分割的連續章節的個數。
第二行到第m+1行,每行告訴了一個信息,該信息說明了哪個課本的第幾章到第幾章必須一次性完成。同一科目給定的章節有可能重複或有重疊。
每一行信息分爲兩個部分。第一部分是「Politics:」、「History:」、「Geography:」三個字符串中的一個;第二部分是用「-」鏈接的兩個數字x,y(1≤x<y≤n),表示該行第一部分所示的課本從第x章到第y章具備連續性。第二部分緊接在第一部分後面,沒有任何符號分隔。
對於30%的數據,n,m≤10;
對於50%的數據,n,m≤1000;
對於100%的數據,n,m≤100 000。
【輸出】
一個正整數,表示按照學校和年級的種種要求,最多能夠安排的階段個數。若是沒有符合條件的安排方案,請輸出-1。
注意:有3個要求須要同時考慮。(1)每個階段包含的各科章數相同;(2)按時間函數計算出的各階段所需天數必須是7的倍數;(3)給出的任一個連續章節都不能被分割開來。
【樣例輸入】
8 3
Politics:1-2
History:5-6
Politics:1-4
【樣例輸出】
3
註釋 Hint
【樣例說明】
最多能夠安排3個階段,具體方案:第一階段完成各科第1~6章的課程;第二階段完成各科第7章的課程;第三階段完成各科第8章的課程。
【樣例輸入#2】
4 2
Geography:1-3
History:2-4
【樣例輸出#2】
-1
【算法分析】
類型:數學+動態規劃。難題!
作法1: 搜索,30分。
作法2: O(n2 )動態規劃,50分。
作法3: O(n)動態規劃,100分,發現上當了。(1)3科能夠合爲1科。(2)設f[i,j]表示將前i章按這樣的要求劃分所能獲得的最多階段數:最後一部分的章節數除以6餘j,且前面的章節按要求劃分。所以,j的取值只能是0~5。
:=f[i-1,0] 當Cont[i-1]=True時;
f[i,1]
:=Max{ f[i-1,0],f[i-1,1],f[i-1,2] }+1 當Cont[i-1]=False時;
f[i,2]:=f[i-1,1];
f[i,3]:=f[i-1,2];
f[i,4]:=f[i-1,3];
f[i,5]:=f[i-1,4];
f[i,0]:=f[i-1,5];
最後輸出f[n,0]、f[n,1]、f[n,2]中的最大值便可。
[程序]
Program d3;
Const L =100000;
var p:array [1..100000] of longint; ok: array [1..100000] of boolean;
f:array [0..100000,0..5] of longint; i,j,n,m,x,y,v,ans: longint;
s,t: string;
begin
assign(input,'d3.in');reset(input);assign(output,'d3.out');rewrite(output);
readln(n,m);
for i:= 1 to m do
begin
readln(s);
v:=pos(':',s);delete(s,1,v);v:=pos('-',s);t:=copy(s,1,v-1);
delete(s,1,v);val(t,x); val(s,y);if p[x]<y then p[x]:=y;
end;
v:=0;
for i:= 1 to n do
begin
if p[i]>v then v:=p[i]; if i<v then ok[i]:=true;
end;
for i:= 1 to 5 do f[0,i]:=-1;
for i:= 1 to n do
begin
if not ok[i] then
begin
f[i,0]:=-1;
if f[i-1,5]<>-1 then f[i,0]:=f[i-1,5]+1;
if (f[i-1,0]<>-1) and (f[i-1,0]+1>f[i,0]) then f[i,0]:=f[i-1,0]+1;
if (f[i-1,1]<>-1) and (f[i-1,1]+1>f[i,0]) then f[i,0]:=f[i-1,1]+1;
end else f[i,0]:=f[i-1,5];
f[i,1]:=f[i-1,0];f[i,2]:=f[i-1,1];
f[i,3]:=f[i-1,2];f[i,4]:=f[i-1,3];
f[i,5]:=f[i-1,4];
end;
if f[n,0]=0 then writeln('-1') else writeln(f[n,0]);
close(input); close(output);
end.
精緻細膩的背景,外加神祕的印加音樂陪襯,仿佛置身在古老國度裏面,進行一個神祕的遊戲——這就是著名的祖瑪遊戲。祖瑪遊戲的主角是一隻石青蛙,石青蛙會吐出各類顏色的珠子,珠子造型美麗,而且有着神祕的色彩,環繞着石青蛙的是載着珠子的軌道,各類顏色的珠子會沿着軌道往前滑動,石青蛙必需遏止珠子們滾進去軌道終點的洞裏頭,如何減小珠子呢?就得要靠石青蛙吐出的珠子與軌道上的珠子相結合,顏色相同者便可以消失得分。直到軌道上的珠子統統都被清乾淨爲止。
或許你並不瞭解祖瑪遊戲,不要緊。這裏咱們介紹一個簡單版本的祖瑪遊戲規則。一條通道中有一些玻璃珠,每一個珠子有各自的顏色,如圖3-24所示。玩家能夠作的是選擇一種顏色的珠子(注意:顏色能夠任選,這與真實遊戲是不一樣的)射入某個位置。
圖3-24 祖瑪示意圖(1)
圖3-25中玩家選擇一顆藍色珠子,射入圖示的位置,因而獲得一個圖3-26的局面。
圖3-25 祖瑪示意圖(2)
圖3-26 祖瑪示意圖(3)
當玩家射入一顆珠子後,若是射入的珠子與其餘珠子組成了3顆以上連續相同顏色的珠子,這些珠子就會消失。例如,將一顆白色珠子射入圖3-27的位置,就會產生3顆眼色相同的白色珠子。這3顆珠子就會消失,因而獲得圖3-28的局面。
圖3-27 祖瑪示意圖(4)
圖3-28 祖瑪示意圖(5)
須要注意的是,圖3-26中的3顆連續的黃色珠子不會消失,由於並無珠子射入其中。
珠子的消失還會產生連鎖反應。當一串連續相同顏色的珠子消失後,若是消失位置左右的珠子顏色相同,而且長度大於2,則能夠繼續消失。例如,圖3-29中,射入一顆紅色珠子後,產生了3顆連續的紅色珠子。當紅色珠子消失後,它左右都是白色的珠子,而且一共有四顆,因而白色珠子也消失了。以後,消失位置的左右都是藍色珠子,共有3顆,因而藍色珠子也消失。最終獲得圖3-30的狀態。
注意:圖3-30中的3顆黃色珠子不會消失,由於藍色珠子消失的位置一邊是紫色珠子,另外一邊是黃色珠子,顏色不一樣。
圖3-29 祖瑪示意圖(6)
圖3-30 祖瑪示意圖(7)
除了上述的狀況,沒有其餘的方法能夠消去珠子。
如今,咱們有一排珠子,須要你去消除。對於每一輪,你能夠自由選擇不一樣顏色的珠子,射入任意的位置。你的任務是射出最少的珠子,將所有珠子消去。
【輸入】
第一行一個整數n(n ≤500),表示珠子的個數。第二行n個整數(32位整數範圍內),用空格分割,每一個整數表示一種顏色的珠子。
【輸出】
一個整數,表示最少須要射出的珠子個數。
【樣例輸入】
9
1 1 2 2 3 3 2 1 1
【樣例輸出】
1
大學實行學分制。每門課程都有必定的學分,學生只要選修了這門課,並經過考覈就能得到相應的學分。學生最後的學分是他各門課學分的總和。每一個學生都要選擇規定數量的課程。其中有些課程能夠直接選修,有些課程須要必定的基礎知識,必須在選了其餘的一些課程的基礎上才能選修。例如,《剝皮術》就必須在選修了《屠龍術》後才能選修。咱們稱《屠龍術》是《剝皮術》的選修課。每門課的直接選修課最多隻有一門。兩門課也可能存在相同的選修課。
每門課都有一個課號,課號依次是1,2,3…。以表3-1爲例說明。
表3-1 選修課學分表
課 號 |
選 修 課 號 |
學 分 |
1 |
無 |
1 |
2 |
1 |
1 |
3 |
2 |
3 |
4 |
無 |
3 |
5 |
2 |
4 |
表3-1中1是2的選修課,即若是要選修2,則1必須已被選過。一樣,要選修3,那麼1和2都必定被選修過。
每一個學生可選的課程總數是必定的,請找出一種方案,使獲得的總學分最多。
【輸入】
第一行包括兩個正整數M、N(中間一個空格),其中M表示總課程數,(1≤M≤1000),N表示每一個學生最多可選的課程總數。(1≤N≤M)。
如下M行每行表明一門課,課號依次是1,2,…,M。每行兩個數,第一個數爲這門課的直接先修課的課號(若不存在則爲0),第二個數爲該門課的學分。學分是不超過10的正整數。測試數據保證學生可以選滿N門課。
【輸出】
第一行只有一個數,即最多可得的學分。
若是M≤99,則如下N行每行一個數,表示學生所選的課程的課號,課號按升序排列。
若是M≥100,則沒必要輸出具體方案。數據保證只有惟一的正確輸出。
【樣例輸入】
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
【樣例輸出】
13
2
3
6
7
【題目描述】
在遙遠的東方,有一個神祕的民族,自稱Y族。他們世代居住在水面上,奉龍王爲神。每逢重大慶典,Y族都會在水面上舉辦盛大的祭祀活動。
咱們能夠把Y族居住地水系當作一個由岔口和河道組成的網絡。每條河道鏈接着兩個岔口,而且水在河道內按照一個固定的方向流動。顯然,水系中不會有環流(如圖3-31中描述一個環流的例子)。
圖3-31 祭祀示意圖
因爲人數衆多的緣由,Y族的祭祀活動會在多個岔口上同時舉行。出於對龍王的尊重,這些祭祀地點的選擇必須很是慎重。準確地說,Y族人認爲,若是水流能夠從一個祭祀點流到另一個祭祀點,那麼祭祀就會失去它神聖的意義。族長但願在保持祭祀神聖性的基礎上,選擇儘量多的祭祀的地點。
【輸入】
輸入文件river.in中第一行包含兩個用空格隔開的整數N、M,分別表示岔口和河道的數目,岔口從1~N編號。接下來M行,每行包含兩個用空格隔開的整數u、v,描述一條鏈接岔口u和岔口v的河道,水流方向爲自u向v。
【輸出】
第一行包含一個整數K,表示最多能選取的祭祀點的個數。接下來一行輸出一種可行的選取方案。對於每一個岔口依次輸出一個整數,若是在該岔口設置了祭祀點,那麼輸出一個1,不然輸出一個0。應確保你輸出的1的個數最多,且中間沒有空格。
接下來一行輸出,在選擇最多祭祀點的前提下,每一個岔口是否可以設置祭祀點。對於每一個岔口依次輸出一個整數,若是在該岔口可以設置祭祀點,那麼輸出一個1,不然輸出一個0。
注意:多餘的空格和換行可能會致使你的答案被判斷爲錯誤答案。
【樣例輸入】
4 4
1 2
3 4
3 2
4 2
【樣例輸出】
2
1010
1011
【樣例說明】
在樣例給出的水系中,不存在一種方法可以選擇3個或者3個以上的祭祀點。包含兩個祭祀點的測試點的方案有兩種:選擇岔口1與岔口3(如樣例輸出第二行),選擇岔口1與岔口4。
水流能夠從任意岔口流至岔口2。若是在岔口2創建祭祀點,那麼任意其餘岔口都不能創建祭祀點,可是在最優的一種祭祀點的選取方案中咱們能夠創建兩個祭祀點,因此岔口2不能創建祭祀點。對於其餘岔口,至少存在一個最優方案選擇該岔口爲祭祀點,因此輸出爲1011。
【評分規則】
對於每一個測試點:(1)若是你僅輸出了正確的被選取的祭祀點個數,那麼你將獲得該測試點30%的分數;(2)若是你僅輸出了正確的被選取的祭祀點個數與一個可行的方案,那麼你將獲得該測試點60%的分數;(3)若是你的輸出徹底正確,那麼你將獲得該測試點100%的分數;
【數據規模】
N≤100
M≤1 000
【算法分析】
這道題能夠搜索、貪心、枚舉、動態規劃、匹配。
數據結構實驗
實驗內容和目的:
掌握幾種基本的數據結構:集合、線性結構、樹形結構等在求解實際問題中的應用,以及培養書寫規範文檔的技巧。學習基本的查找和排序技術。讓咱們在實際上機中具備編制至關規模的程序的能力。養成一種良好的程序設計風格。
實驗教材:
數據結構題集(C語言版) 清華大學出版社 2007年
實驗項目:
實驗1、棧和循環隊列
㈠、實驗內容:
棧
掌握棧的特色(先進後出FILO)及基本操做,如入棧、出棧等,棧的順序存儲結構和鏈式存儲結構,以便在實際問題背景下靈活應用。本程序採用的是鏈棧結構,具備初始化一個棧、PUSH、POP、顯示全部棧裏的元素四個功能。
循環隊列
掌握隊列的特色(先進先出FIFO)及基本操做,如入隊、出隊等,學會循環隊列的實現,以便在實際問題背景下靈活運用。本程序具備初始化一個隊列、入隊、出隊、顯示隊列的全部元素、隊列長度五個功能。
㈡、實驗代碼
棧
程序代碼:
#include <stdio.h>
#include <malloc.h>
#define Stack_Size 6
#define ERROR 0
#define OK 1
typedef int SElemType;
typedef struct SNode
{
SElemType data;
struct SNode *next;
數據結構實驗
實驗內容和目的:
掌握幾種基本的數據結構:集合、線性結構、樹形結構等在求解實際問題中的應用,以及培養書寫規範文檔的技巧。學習基本的查找和排序技術。讓咱們在實際上機中具備編制至關規模的程序的能力。養成一種良好的程序設計風格。
實驗教材:
數據結構題集(C語言版) 清華大學出版社 2007年
實驗項目:
實驗1、棧和循環隊列
㈠、實驗內容:
棧
掌握棧的特色(先進後出FILO)及基本操做,如入棧、出棧等,棧的順序存儲結構和鏈式存儲結構,以便在實際問題背景下靈活應用。本程序採用的是鏈棧結構,具備初始化一個棧、PUSH、POP、顯示全部棧裏的元素四個功能。
循環隊列
掌握隊列的特色(先進先出FIFO)及基本操做,如入隊、出隊等,學會循環隊列的實現,以便在實際問題背景下靈活運用。本程序具備初始化一個隊列、入隊、出隊、顯示隊列的全部元素、隊列長度五個功能。
㈡、實驗代碼
棧
程序代碼:
#include <stdio.h>
#include <malloc.h>
#define Stack_Size 6
#define ERROR 0
#define OK 1
typedef int SElemType;
typedef struct SNode
{
SElemType data;
struct SNode *next;
數據結構實驗
實驗內容和目的:
掌握幾種基本的數據結構:集合、線性結構、樹形結構等在求解實際問題中的應用,以及培養書寫規範文檔的技巧。學習基本的查找和排序技術。讓咱們在實際上機中具備編制至關規模的程序的能力。養成一種良好的程序設計風格。
實驗教材:
數據結構題集(C語言版) 清華大學出版社 2007年
實驗項目:
實驗1、棧和循環隊列
㈠、實驗內容:
棧
掌握棧的特色(先進後出FILO)及基本操做,如入棧、出棧等,棧的順序存儲結構和鏈式存儲結構,以便在實際問題背景下靈活應用。本程序採用的是鏈棧結構,具備初始化一個棧、PUSH、POP、顯示全部棧裏的元素四個功能。
循環隊列
掌握隊列的特色(先進先出FIFO)及基本操做,如入隊、出隊等,學會循環隊列的實現,以便在實際問題背景下靈活運用。本程序具備初始化一個隊列、入隊、出隊、顯示隊列的全部元素、隊列長度五個功能。
㈡、實驗代碼
棧
程序代碼:
#include <stdio.h>
#include <malloc.h>
#define Stack_Size 6
#define ERROR 0
#define OK 1
typedef int SElemType;
typedef struct SNode
{
SElemType data;
struct SNode *next;
數據結構實驗
實驗內容和目的:
掌握幾種基本的數據結構:集合、線性結構、樹形結構等在求解實際問題中的應用,以及培養書寫規範文檔的技巧。學習基本的查找和排序技術。讓咱們在實際上機中具備編制至關規模的程序的能力。養成一種良好的程序設計風格。
實驗教材:
數據結構題集(C語言版) 清華大學出版社 2007年
實驗項目:
實驗1、棧和循環隊列
㈠、實驗內容:
棧
掌握棧的特色(先進後出FILO)及基本操做,如入棧、出棧等,棧的順序存儲結構和鏈式存儲結構,以便在實際問題背景下靈活應用。本程序採用的是鏈棧結構,具備初始化一個棧、PUSH、POP、顯示全部棧裏的元素四個功能。
循環隊列
掌握隊列的特色(先進先出FIFO)及基本操做,如入隊、出隊等,學會循環隊列的實現,以便在實際問題背景下靈活運用。本程序具備初始化一個隊列、入隊、出隊、顯示隊列的全部元素、隊列長度五個功能。
㈡、實驗代碼
棧
程序代碼:
#include <stdio.h>
#include <malloc.h>
#define Stack_Size 6
#define ERROR 0
#define OK 1
typedef int SElemType;
typedef struct SNode
{
SElemType data;
struct SNode *next;
數據結構實驗
實驗內容和目的:
掌握幾種基本的數據結構:集合、線性結構、樹形結構等在求解實際問題中的應用,以及培養書寫規範文檔的技巧。學習基本的查找和排序技術。讓咱們在實際上機中具備編制至關規模的程序的能力。養成一種良好的程序設計風格。
實驗教材:
數據結構題集(C語言版) 清華大學出版社 2007年
實驗項目:
實驗1、棧和循環隊列
㈠、實驗內容:
棧
掌握棧的特色(先進後出FILO)及基本操做,如入棧、出棧等,棧的順序存儲結構和鏈式存儲結構,以便在實際問題背景下靈活應用。本程序採用的是鏈棧結構,具備初始化一個棧、PUSH、POP、顯示全部棧裏的元素四個功能。
循環隊列
掌握隊列的特色(先進先出FIFO)及基本操做,如入隊、出隊等,學會循環隊列的實現,以便在實際問題背景下靈活運用。本程序具備初始化一個隊列、入隊、出隊、顯示隊列的全部元素、隊列長度五個功能。
㈡、實驗代碼
棧
程序代碼:
#include <stdio.h>
#include <malloc.h>
#define Stack_Size 6
#define ERROR 0
#define OK 1
typedef int SElemType;
typedef struct SNode
{
SElemType data;
struct SNode *next;