NOIP普及組,試題分析教案准備
冬令營課程安排提綱
第一天、試題分析安排
第一題,桃桃摘蘋果。個人認為不應該花時間講這道題,而主要強調題中的細節部分,不如碰到即為摘到蘋果這樣的情況,這裡的細心決定了這到題的成敗。再強調一下檔案讀取的基本操作。特別是對檔案讀寫後的關閉操作。以及在這種檔案讀寫題中,最後要將檔案除錯過程中的對螢幕輸出和用readln;進行等待等語句去掉,否則因為這些問題影響成績太不值得。
準備,讓學生做一下提高組第一題,難度不大,但是細心程度要有所增加。
如下:
【問題描述】
某校的慣例是在每學期的期末考試之後發放獎學金。發放的獎學金共有五種,獲取的條件各自不同:
1)院士獎學金,每人8000元,期末平均成績高於80分(>80),並且在本學期內發表1篇或1篇以上論文的學生均可獲得;
2)五四獎學金,每人4000元,期末平均成績高於85分(>85),並且班級評議成績高於80分(>80)的學生均可獲得;
3)成績優秀獎,每人2000元,期末平均成績高於90分(>90)的學生均可獲得;
4)西部獎學金,每人1000元,期末平均成績高於85分(>85)的西部省份學生均可獲得;
5)班級貢獻獎,每人850元,班級評議成績高於80分(>80)的學生幹部均可獲得;
只要符合條件就可以得獎,每項獎學金的獲獎人數沒有限制,每名學生也可以同時獲得多項獎學金。例如姚林的期末平均成績是
現在給出若干學生的相關資料,請計算哪些同學獲得的獎金總數最高(假設總有同學能滿足獲得獎學金的條件)。
【輸入檔案】
輸入檔案scholar.in的第一行是一個整數N(1 <= N <= 100),表示學生的總數。接下來的N行每行是一位學生的資料,從左向右依次是姓名,期末平均成績,班級評議成績,是否是學生幹部,是否是西部省份學生,以及發表的論文數。姓名是由大小寫英文字母組成的長度不超過20的字串(不含空格);期末平均成績和班級評議成績都是
【輸出檔案】
輸出檔案scholar.out包括三行,第一行是獲得最多獎金的學生的姓名,第二行是這名學生獲得的獎金總數。如果有兩位或兩位以上的學生獲得的獎金最多,輸出他們之中在輸入檔案中出現最早的學生的姓名。第三行是這N個學生獲得的獎學金的總數。
【樣例輸入】
4
YaoLin 87 82 Y N 0
ChenRuiyi 88 78 N Y 1
LiXin 92 88 N N 0
ZhangQin 83 87 Y N 1
【樣例輸出】
ChenRuiyi
9000
28700
第二題,校園外的樹。題目設計的重複區域問題,只是希望能夠避開,用計算每個區域內的樹,然後總和得到總數目的情況。當然按照測試資料的情況,統計總數這種方法,也可以拿到20分。但是對於這樣簡單的題一定要拿到滿分。經過分析資料的規模,我們可以考慮到規模並不是很大,那麼我們就可以用統計法來做這道題,這樣重複區域就對實際解題沒有任何影響了。簡單的定義一個一維陣列用來做標記,然後再統計就可以滿足題目要求。
program tree;
uses crt;
var
fi,fo:text;
long,m,total:word;
a,b:array[1..100] of word;
i,jian:integer;
all:array[0..10000] of 0..1;
begin
clrscr;
total:=0;
for i:=0 to 10000 do
all[i]:=1;
assign(fi,'tree.in');
reset(fi);
read(fi,long);
read(fi,m);
readln(fi);
for i:=1 to m do
begin
read(fi,a[i]);
read(fi,b[i]);
readln(fi);
end;
for i:=1 to m do
begin
for jian:=a[i] to b[i] do
begin
all[jian]:=0;
end;
end;
for i:=0 to long do
begin
if all[i]=1 then
inc(total);
end;
assign(fo,'tree.out');
rewrite(fo);
writeln(fo,total);
close(fi);
close(fo);
end.
第三題,採藥。這道題還是有難度的。基本解決思路有幾種,貪心,模擬和動態規劃。貪心演算法能夠解決舉例的資料,但是測試資料則無法通過。模擬演算法,可以通過30%的資料,即資料小與10個一下的測試資料。而對於100%的測試資料則嚴重超時無法計算。動態規劃演算法,解決較快,而且有效。
給出幾種演算法的大概思路,引導學生用三種演算法完成這道題,體會演算法的不同。
(1)貪心
program medic(input,output);
var f1,f2:text;
med_t:array[1..100] of integer;
med_v:array[1..100] of integer;
med:array[1..100] of real;
i,j,k,time,z,vale:integer;
zz:real;
begin
assign(f1,'medic.in');
assign(f2,'medic.out');
reset(f1);
rewrite(f2);
read(f1,time,j);
for i:=1 to j do
begin
read(f1,med_t[i],med_v[i]);
med[i]:=med_v[i]/med_t[i];
end;
for i:=1 to j-1 do{排序}
for k:=i+1 to j do
if med[i]<med[k] then begin z:=med_t[i];
med_t[i]:=med_t[k];
med_t[k]:=z;
z:=med_v[i];
med_v[i]:=med_v[k];
med_v[k]:=z;
zz:=med[i];
med[i]:=med[k];
med[k]:=zz;
end;
vale:=0;
for i:=1 to j do
begin
if med_t[i]<=time then begin
vale:=vale+med_v[i];
time:=time-med_t[i];
end;
if time=0 then break;
end;
write(f2,vale);
close(f1);
close(f2);
end.
上面是一個用貪心演算法完成的習題。但是,我們很容易找到貪心演算法的反例,而且貪心演算法是隻能在區域性最優的基礎上,找出整體次優的解法。所以它很難適合現代競賽的要求,但是是否貪心演算法就毫無用處呢?其實不然,在現實生活中,因為貪心演算法是最接近人們思維方式的演算法,所以在設計很多方面的問題(特別是經濟方面的問題時,有很大的應用)。下面我們闡述一下有關貪心演算法的一個重要應用——找零錢問題。
引入到硬幣題中,我們可以為國家貨幣發行機關制定這樣的假設:每次找零要求硬幣數最小。
那麼由此產生下面這些想法:
1,首先究竟貪心法的正確率怎麼樣?
事實和理論都已經證明,貪心法是一種漸近最優解,它未必是最優的解。事實確實是這樣,考慮下面一種硬幣面值組合1、3、4,當需要找零6的時候,貪心演算法會按照4、1、1的方案,而事實上,3、3的方案才是最優解。那麼我們馬上會想到,是不是最優解會在最大面值和第二面值兩者之一產生呢?
事實也證明這也只是猜想,考慮1、8、9、11這四種面值的硬幣,要找零24的時候,首先產生解11、11、1、1,然後是解9、9、1、1、1、1、1、1,而實際上8、8、8才是最優解。
於是我們可以知道,這種機制是沒有辦法產生確定的最優解的。
2,接下來的問題是:要滿足怎麼樣條件的面值組合,才能夠在所有情況下能用貪心法來求解呢?
首先考慮我們實際存在的硬幣組合1、2、5,幾乎所有的情況下,它都不會造成誤解,
1=1
2=2
3=1+2
4=2+2
5=5
6=1+5
7=2+5
8=1+2+5
9=2+2+5
那我們再來考慮1、2、4這個組合
1=1
2=2
3=1+2
4=4
5=1+4
6=2+4
7=1+2+4
8=4+4
9=4+4+1
我們可以發現,為了表現1-9這9種金額,1、2、4和1、2、5的平均找零硬幣個數是相等的。
如果我們在1、2、4中再新增一個8(這是很容易讓人聯想到的),會不會有什麼新奇的結果呢?
事實上,如果我們沒有10元鈔票的話,新增一個8元的鈔票確實能夠減少平均找零硬幣個數,但不幸的是,我們使用十進位制,所以加入一個8元的面值硬幣對我們並沒有什麼太大的顯著改進。但是不可否認,從這一點上我們可以發現一些規律。
3,考慮完上述數學邏輯上的問題以後,我們把目光再放回到實際的問題上,我們已經制定了1、2、5的組合策略,現在讓我們來想一想,為什麼這個策略被選中了呢?
那是因為(正如上文已經說過的)我們使用的十進位制,因此在124與125這類的面值都能夠很好的滿足貪心演算法的前提下,我們當然會更願意選擇125這種方案,因為i10=5*2,更加讓人心裡覺得舒服。
因此,我們可以把硬幣面值制定策略所要遵循的規則總結為:必須滿足貪心演算法(因為大多數人可以使用這種比較少費腦子的方法進行計算),必須在心理上儘量滿足人們對於十進位制運算的方便性考慮(這也是125方案被選中的原因)
(2)模擬
program medic_moni;
uses crt;
var
fi,fo:text;
i,j,k,b:integer;
a:array[1..105] of 0..1;
tt,vv:array[1..100] of integer;
t,m:integer;
total1:integer;
value,max,time:longint;
begin
clrscr;
value:=0;max:=0;
assign(fi,'medic.in');
reset(fi);
assign(fo,'medic.out');
rewrite(fo);
read(fi,t);
read(fi,m);
readln(fi);
for i:=1 to m do
begin
read(fi,tt[i]);
read(fi,vv[i]);
readln(fi);
end;
for i:=1 to m do
a[i]:=0;
repeat
k:=m;
while (a[k]=1) do
begin
a[k]:=0;
dec(k);
end;
a[k]:=1;
time:=0;
value:=0;
for j:=1 to m do
begin
time:=time+tt[j]*a[j];
value:=value+vv[j]*a[j]
end;
if (time<=t) and (value>max) then
max:=value;
total1:=0;
for i:=1 to m do
if a[i]=1 then
inc(total1);
until total1=m;
writeln(fo,max);
close(fi);
close(fo);
end.
(3)動態規劃
program medic;
uses crt;
var
fi,fo:text;
tt,vv:array[0..200] of integer;
dp:array[0..1005,0..105] of integer;
t,m:integer;
i,j:integer;
a,b:integer;
begin
clrscr;
assign(fi,'medic.in');
reset(fi);
assign(fo,'medic.out');
rewrite(fo);
read(fi,t);
read(fi,m);
readln(fi);
for i:=1 to m do
begin
read(fi,tt[i]);
read(fi,vv[i]);
readln(fi);
end;
for i:=1 to t do
begin
for j:=1 to m do
begin
a:=dp[i][j-1];
if i>=tt[j] then
begin
b:=dp[i-tt[j]][j-1]+vv[j];
if b>a then
a:=b;
end;
dp[i][j]:=a;
end;
end;
writeln(fo,dp[t][m]);
close(fi);
close(fo);
end.
第四題,主要是應用高精度演算法,加上一些優化演算法,程式如下:
Program Circle;
Type
Arr=Array[1..101] Of Integer;
Var
i,j,p,k,code,L,num,tp:Integer;
s: String;
a,aa,time,b,temp:Arr;
Procedure Mutiply(a, b:Arr;Var c:Arr;t:Integer);
Var
i, j: Integer;
Begin
FillChar(c, SizeOf(c), 0);
For i:=1 To t Do
For j:=1 To t-i+1 Do
Begin
c[i+j-1]:=a[i]*b[j]+c[i+j-1];
c[i+j]:=c[i+j]+c[i+j-1] Div 10;
c[i+j-1]:=c[i+j-1] Mod 10;
End;
End;
Procedure Mutiply(a:Arr;b:Integer;Var c:Arr;Var L:Integer);
Var
i: Integer;
Begin
FillChar(c,SizeOf(c),0);
For i:=1 To L Do
Begin
c[i]:=c[i]+a[i]*b;
c[i+1]:=c[i] Div 10;
c[i]:=c[i] Mod 10;
End;
If c[L+1]<>0 Then
Inc(L);
End;
Begin
Assign(Input, 'circle.in');
Assign(Output, 'circle.out');
ReSet(Input);
ReWrite(Output);
ReadLn(s);
Val(Copy(s, Pos(' ', s)+1, Length(s)-Pos(' ', s)), k, code);
Delete(s, Pos(' ', s), Length(s)-Pos(' ', s)+1);
For i:=1 To Length(s) Do
Val(s[i], a[Length(s)-i+1], code);
aa:=a;