1. 程式人生 > >NOIP普及組,試題分析教案准備

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)的學生幹部均可獲得;

    只要符合條件就可以得獎,每項獎學金的獲獎人數沒有限制,每名學生也可以同時獲得多項獎學金。例如姚林的期末平均成績是

87分,班級評議成績82分,同時他還是一位學生幹部,那麼他可以同時獲得五四獎學金和班級貢獻獎,獎金總數是4850元。

現在給出若干學生的相關資料,請計算哪些同學獲得的獎金總數最高(假設總有同學能滿足獲得獎學金的條件)。

【輸入檔案】

    輸入檔案scholar.in的第一行是一個整數N1 <= N <= 100),表示學生的總數。接下來的N行每行是一位學生的資料,從左向右依次是姓名,期末平均成績,班級評議成績,是否是學生幹部,是否是西部省份學生,以及發表的論文數。姓名是由大小寫英文字母組成的長度不超過20的字串(不含空格);期末平均成績和班級評議成績都是

0100之間的整數(包括0100);是否是學生幹部和是否是西部省份學生分別用一個字元表示,Y表示是,N表示不是;發表的論文數是010的整數(包括010)。每兩個相鄰資料項之間用一個空格分隔。

【輸出檔案】

    輸出檔案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,首先究竟貪心法的正確率怎麼樣?

事實和理論都已經證明,貪心法是一種漸近最優解,它未必是最優的解。事實確實是這樣,考慮下面一種硬幣面值組合134,當需要找零6的時候,貪心演算法會按照411的方案,而事實上,33的方案才是最優解。那麼我們馬上會想到,是不是最優解會在最大面值和第二面值兩者之一產生呢?

事實也證明這也只是猜想,考慮18911這四種面值的硬幣,要找零24的時候,首先產生解111111,然後是解99111111,而實際上888才是最優解。

於是我們可以知道,這種機制是沒有辦法產生確定的最優解的。

 

2,接下來的問題是:要滿足怎麼樣條件的面值組合,才能夠在所有情況下能用貪心法來求解呢?

首先考慮我們實際存在的硬幣組合125,幾乎所有的情況下,它都不會造成誤解,

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

那我們再來考慮124這個組合

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-99種金額,124125的平均找零硬幣個數是相等的。

如果我們在124中再新增一個8(這是很容易讓人聯想到的),會不會有什麼新奇的結果呢?

事實上,如果我們沒有10元鈔票的話,新增一個8元的鈔票確實能夠減少平均找零硬幣個數,但不幸的是,我們使用十進位制,所以加入一個8元的面值硬幣對我們並沒有什麼太大的顯著改進。但是不可否認,從這一點上我們可以發現一些規律。

 

3,考慮完上述數學邏輯上的問題以後,我們把目光再放回到實際的問題上,我們已經制定了125的組合策略,現在讓我們來想一想,為什麼這個策略被選中了呢?

那是因為(正如上文已經說過的)我們使用的十進位制,因此在124125這類的面值都能夠很好的滿足貪心演算法的前提下,我們當然會更願意選擇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;