NOIP 2010 普及組解題報告
NOIP 2010普及組解題報告
北京市八一中學 王祺磊
感謝:
我也學習一下,現在的流行趨勢,先開始感謝,但這真的是發自內心的感覺,讓我發出一下這些感謝。
一、感謝和我一起並肩戰鬥資訊學奧賽教學一線的老師們,是你們給了我溫暖,讓我感受到一個溫馨的集體,在其中,我們一起學習,一起研討;
二、感謝一直和我一起戰鬥在一起的孩子們,是你們跟我一起,在風雨中奮鬥,一起成長;
三、感謝我的幾位師長,是你們一直在我成長的過程中,指引我前進的方向;
四、特別感謝,幾位與我一起成長起來的“小牛”,是你們,反饋給了我很多需要學習的知識,讓我逐漸成熟。
關於2010年普及組考題:
沒有想到,這次的題目和2008、2009年的題目完全風格兩樣,我不知道是不是出題老師發生了變化。但是,總體感覺,題目趨於容易,甚至沒有涉及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]與我交流。