1. 程式人生 > >NOIP 2010 普及組解題報告

NOIP 2010 普及組解題報告

NOIP 2010普及組解題報告

北京市八一中學 王祺磊

感謝:

我也學習一下,現在的流行趨勢,先開始感謝,但這真的是發自內心的感覺,讓我發出一下這些感謝。

一、感謝和我一起並肩戰鬥資訊學奧賽教學一線的老師們,是你們給了我溫暖,讓我感受到一個溫馨的集體,在其中,我們一起學習,一起研討;

二、感謝一直和我一起戰鬥在一起的孩子們,是你們跟我一起,在風雨中奮鬥,一起成長;

三、感謝我的幾位師長,是你們一直在我成長的過程中,指引我前進的方向;

四、特別感謝,幾位與我一起成長起來的“小牛”,是你們,反饋給了我很多需要學習的知識,讓我逐漸成熟。

 

關於2010年普及組考題:

沒有想到,這次的題目和20082009年的題目完全風格兩樣,我不知道是不是出題老師發生了變化。但是,總體感覺,題目趨於容易,甚至沒有涉及DP、搜尋中的初級方法。但是,題目很多知識點又隱含頗多。不容易拿到完美的分數。感謝出題老師,給我們帶來的這屆優秀題目。

 

題目分析:

一、數字統計

此題,曾經在某OJ上看到過原題。要求和實現都非常簡單。無非是,枚舉出所有在範圍內的數字,然後對數字進行拆分,對每一位數字進行判斷。

一個樸素的for迴圈,巢狀一個while迴圈,就可以解決這道題目。

下面是程式的核心部分:

for ( i = l ; i <= r ; i++ )

{

t = i;

    while (t > 0)

    {

          y = t % 10;

          if (y == 2) s++;

          t /= 10;

   

}

}

此題丟分,絕對可以認為是絕不應該出現的事情。往參賽選手能夠更加註意自己的程式細節,避免問題出現。

 

二、接水問題

問題描述隱晦,引導學生朝著純列舉的思想前進,部分用秒列舉的學生會導致嚴重超時。但題目的核心思想卻應該是模擬問題發生的本質順序。也就是,每個新加入的人去接水的位置,一定是當前數列中和最小的那列。所以實現的方法,就是每個新數字,進入前,找出當前序列中最小的位置,加入進去。直到所有的數字都加入進去後結束。最終從所有的數字中,找出最大的那個值,即為所求。

但是,其實有更合適的模型——插入法排序。

即從大到小排序後,無非就是把數字加入到最後一個數值之上,然後運用插入法排序原理,把這個新數插入到合適位置。最終輸出的是陣列的最大值即可。

下面是程式的核心部分:

首先,對前m個數進行排序,然後後面的n m個數,就需要模擬插入了:

    for (i = m ; i < n ; i++ )

    {

      cin >> t;

      t += a[m - 1];

      j = m - 1;

      while (j > 0 && a[j - 1] < t)

      {

        a[j] = a[j - 1];

        j--;

      }

      a[j] = t;

    }

最後a[0]一定是最大值。

非常想用這道題,告訴那些忽視插入法排序的學生,當你寫不好快排的時候,插入法,幫我們解決了很多為了一個數值而要排全部數值的問題。雖然效率和選擇、冒泡一樣,但插入法確實有它特殊之處。

 

三、導彈攔截

再次見到導彈攔截,頗感親切,但是這次的導彈攔截,加入了立體環節,也從借用原題概念,讓一個不存在的動態規劃方法,影響解題思路。

其實,題目的核心解題思想還是貪心和列舉。只不過要列舉的有些策略而已。我也曾試過了,用單位長度去列舉兩個點的半徑,結果殘酷的只過了兩個點。還是經過學生的解釋,明白了排序後的貪心策略。所以,向學生學習,也是老師的必修課程。我再次強調,我經常向學生學習,教學相長,不外乎如此。所以,希望更多的老師,能夠時常放下自己的架子和身份,多多向學生請教,我們共同的成長。

按照到第一個點的距離平方排序之後,就可以不斷讓最遠的點,不用離開第一點半徑,進入第二點半徑。在這個過程中,第一點半徑逐漸縮小,第二點半徑,可能發生增大。就需要一步步統計出,兩個半徑平方的最小值。最終達到題目要求。

因為考慮到點的數量是100000,所以,無比需要使用快速排序。題目如果想寫的較為簡潔,還是使用結構體比較方便。

首先定義一個結構體,包括x,y,jr1(距離第1點半徑平方),jr2(距離第2點半徑平方)。

struct dian

{

  int x;

  int y;

  int j1r;

  int j2r;

}d[100050];

這裡面順便定義了一個10萬數量級的陣列。

 

 

快速排序的函式:

void qsort(int s, int e)

{

  dian t;

  int l, r;

  if (s >= e) return ;

  l = s;

  r = e;

  t = d[s];

  while (l < r)

  {

    while (l < r && d[r].j1r >= t.j1r) r--;

    d[l] = d[r];

    while (l < r && d[l].j1r <= t.j1r) l++;

    d[r] = d[l];

  }

  d[r] = t;

  qsort(s, r - 1);

  qsort(r + 1, e);

}

然後就是逐漸退出和更新半徑的過程了:

r = d[n].j1r;

    m2 = 0;

    for (i = n ; i >= 1 ; i--)

    {

        if (d[i].j2r > m2) m2 = d[i].j2r;

        tr = d[i - 1].j1r + m2;

        if ( r > tr ) r = tr;

    }

最後r即為最小消耗。

四、三國遊戲

從思想實現來說,還是一道列舉加貪心的題目。因為,“人”在和計算機鬥爭的過程中,雙方都無法取得最大的默契配合值。所以,“人”只能夠去考慮次大最優值,但是,為了能夠騙過計算機,我們選擇的,只能夠是跟我們選擇的第一位武將配合值次大的那位武將(如果不是一個的話也會被計算機破壞掉)。所以,就要求我們必須,找到,每行中次大值最大的那個值(挺擾人的)。當然,這個提法是建立在我們有了題目中展示的那個矩陣之後。你要注意,題目給你的值是這個矩陣的右半邊,你需要把左半邊的值自己不上,才能夠按照行去列舉(當然,做完後,如果你想按照列,也沒有關係,因為全都是對稱的,對吧)。

當然,可能有同學考慮到了0的難問題。很遺憾,這道題肯定不會出現零。因為,計算機只能破壞“人”,而且,我也想到了騙他的方法,計算機必輸無疑。

下面看一下程式的核心部分:

輸入部分:

    for (i = 1 ; i < n ; i++)

      for (j = i + 1 ; j <= n ; j++ )

        cin >> s[i][j];

對稱複製部分:

    for (i = 2 ; i <= n ; i++)

      for (j = 1 ; j < i ; j++)

        s[i][j] = s[j][i];

找每行次大值中的最大值部分:

    for (i = 1 ; i <= n ; i++)

    {

      t1 = t2 = 0;

      for (j = 1 ; j <= n ; j++)

      {

        if (s[i][j] > t1) t1 = s[i][j];

        if (t1 > t2) swap(t1, t2);

      }

      if (t < t1) t = t1;

    }

很明顯,t為每行次大值(t1)的最大值。也就是我們的所求。

本題,最重要的是想到解決的方法,一旦方法確定,程式實現,相當簡單。也望各位選手能夠逐漸提高自己的思維方式。讓自己的能力逐漸提升。

 

沒想到這麼快寫出解題報告,其中不足之處,望看到的高手不吝賜教。也希望我的學生們可以更快的提高自己。能夠更好的為將來的發展早做準備。

如您願意給我發郵件,歡迎發郵件至:[email protected]與我交流。