2015年南海區初中資訊學競賽試題解題報告
第一題 危險的實驗(dangerous)
【題意分析】
將n個化學物質放在桌子上,給出它們與附近化學物質的安全距離,請問最短需要多長的桌子。
【資料範圍】
20%的資料,1<=n<=20
50%的資料,1<=n<=100000
100%的資料,1<=n<=1000000,1<=ai<=100000
【試題難度】
★☆☆☆☆
【解題思路】
這題要注意:化學物質不能排序(按輸入順序)。
這是第一題,應該說分析好就容易了。這一題只需要將相鄰的兩個最大值求出來,全部加起來就可以了。這一題可以不用陣列,直接將兩個相鄰的用臨時變數記錄下來即可。(本題不需要過多考慮時間和空間限制,因為資料不大)
【解題反思】
1.這題主要是看題,分析題,要仔細。
【參考程式】
#include<fstream> #include<iostream> using namespace std; ifstream fin("dangerous.in"); ofstream fout("dangerous.out"); #define cin fin #define cout fout int n,x,y,maxx=-1,minn=100001; //最大值初始值應為0以下,最小值則相反 long long ans=0; int main() { cin >> n; cin >> x; //提前讀入第一個數,以便判斷 for (int i=1;i<n;i++) { cin >> y; ans+=max(x,y); //取兩個相鄰的化學物質距離的較大值相加 x=y; //將現在的第二個數變為下一個的第一個數 } cout << ans << endl; return 0; }
第二題 眾數(mode)
【題意分析】
給出n個數,求其中出現次數最多的所有數。
【資料範圍】
40%的資料,1<=n<=400
100%的資料,1<=n<=1000000,1<=ai<=1000000000
【試題難度】
★★☆☆☆
【解題思路】
這題用計數排序不太現實,因為如果你想拿全分,那就必須“壓縮”思想。計數排序是在知道資料範圍時生成一些桶(並不管它們是否有用),這樣就浪費了時間和空間。所以我們們可以開兩個陣列,將排好序後的數進行統計,裝入桶中,雖然可能時間稍長(不超時),但不會空間溢位。
【解題反思】
1.看清空間、時間限制,估計空間、時間。
2.要學會想最優演算法。
【參考程式】
#include<fstream> #include<algorithm> using namespace std; ifstream fin("mode.in"); ofstream fout("mode.out"); #define cin fin #define cout fout long long n=0,ai[1000000],a[1000000],maxx=-1,k=0; int main() { cin >> n ; for (int i=0;i<n;i++)cin >> a[i]; sort(a+0,a+n); //將數進行快排 int temp=0; for (int i=0;i<n;i++) { while (a[i]==a[i+1]) {i++;temp++;} //將相同的數統計出來 if (temp+1>maxx) maxx=temp; //取最大值 temp=0; } for (int i=0;i<n;i++) { while (a[i]==a[i+1]) {i++;temp++;} if (temp==maxx) {ai[k]=i;k++;} //如果數的個數等於最大值,就記錄下來 temp=0; //注意清零 } cout << k << endl; //打出眾數個數 for (int i=0;i<k;i++) cout << a[ai[i]] <<' '; return 0; }
第三題 樹(tree)
【題意分析】
給出一個無向圖的點數n,以及它的m條邊,這個無向圖中有多少棵樹。
【資料範圍】
20%的資料,1<=n<=2000
100%的資料,1<=n<=100000,0<=m<=min(n*(n-1)/2,200000)
【試題難度】
★★★★☆
【解題思路】
這題是關於並查集的應用。
並查集指的是一種“並”與“查”的集合,是通過一個數組來實現的,核心思想就是:陣列元素所存放的值,代表他的上一級(通俗一點就是它的“父親”)。如果它是root,怎麼辦?有兩種方法判斷:①一開始將所有元素(點)的上一級設為自己,如果自己的上一級等於自己就說明它是祖先(root)。②一開始將所有元素(點)的上一級設為-1,如果自己的上一級等於-1就說明它是祖先(root)。這樣就可以向上查詢,實現方法如下:
while (fa[root] != root) root = fa[root]; //可以替換為遞迴,意思是一直向上找,找到root(變數)為root(祖先)為止
合併應該怎樣做?有了查,就很容易合併:將a點所在的連通塊與b點所在的連通塊的root(祖先)進行比較,如果不相同就將b的root改為a的root(反過來也可以,因為它是無向圖,只在乎是否為同一個連通塊)。(詳細程式請參考【參考程式】)
講完前面,再回過頭來分析題目。題目有一點必須注意“一個連通塊是樹,當且僅當邊數比點數少1”,這說明連通塊的數量並不等於樹的數量,應再根據上述條件進行判斷,也就是記錄邊數與點數。(點數初始值為1,邊數初始值為0,因為本身有一個點)合併時要注意,新的這個連通塊的邊數,應為原來兩個連通塊的邊數之和再加1,因為合併它們倆時還增加了一條邊,點數則不用加1。
放到lemon裡一測試,問題就出來了,為什麼會超時?讓我們看回題目資料範圍n<=100000!!如果我們將這棵樹連線成線狀,最後一個點的查詢將執行100000次while!!所以這一題還需要用到“壓縮路徑”。意思就是將很長的路徑縮短,直接連到root,查詢就會十分快速。可能你會問,那這樣不就改變了樹的結構和形狀?的確,不過並沒有關係,因為題目只是要求求出樹的數量,不需要將原樹保留,所以這樣可以有效地節省時間,實現方法如下:
while (fa[x]!=x) { int y = fa[x]; fa[x] = root; x = y;}
【解題反思】
1.看題目要認真,分析題目並想好基礎演算法。
2.做完題目後有多餘時間可以想一想最優演算法。
【參考程式】
#include<iostream>
#include<fstream>
#include<algorithm>
using namespace std;
ifstream fin("tree.in");
ofstream fout("tree.out");
#define cin fin
#define cout fout
int n,m,fa[1000001],ans=0;
int u_f(int x)
{
int root = x;
while (fa[root]!=root && fa[root]!=-1) root=fa[root]; //找root
while (fa[x]!=x && fa[x]!=-1) { int y = fa[x]; fa[x] = root; x = y;} //壓縮路徑
return root;
}
int main()
{
cin >> n >> m;
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=0;i<m;i++)
{
int x,y;
cin >> x >> y;
int xfa=u_f(x);int yfa=u_f(y); //求出x和y的祖先(root)
if (xfa!=yfa) fa[yfa]=xfa;
else fa[xfa]=-1; //如果x和y已在同一個連通塊上,再將它們連線就說明它已不是樹,將它修改為-1(將這個連通塊的root廢掉)
}
for (int i=1;i<=n;i++) if (fa[i]==i) ans++; //如果它的祖先是自己,就說明他是一個祖先(root)。(非樹的root為-1,已排除)
cout << ans << endl;
return 0;
}
(對本程式的解釋:這是優化後的程式,將原來的記錄點數、邊數,改為了:如果x和y本來就是一個連通塊,說明這已不是一棵樹(一個連通塊是樹,當且僅當邊數比點數少1),所以可以直接廢掉root,掃描時就不會將它算為樹。)
第四題 字串
【題意分析】
給出一個字串s和t,求s中有多少個不重複的t。
【資料範圍】
50%的資料,1<=字串T長度<=20000, 1<=字串S長度<=100
100%的資料,1<=字串T長度<=1000000, 1<=字串S長度<=100000。其中多數是隨機產生。
【試題難度】
★★★☆☆
【解題思路】
這一題主要的問題就是超時,用“find”和“erase”就會超時,所以我們應該用模擬“find”的原理,可以將時間減短許多。(因為t的那一段,如果用find,將會掃描兩遍,時間在t很長時容易超時,find呼叫也需要時間)。所以我們可以一位一位往前找,直到完全匹配為止,否則重新從t開頭進行匹配。
【解題反思】
1.要認真分析估算時間是否足夠。
2.檢查時要製作資料,進行測試,不能盲目自信。
【參考程式】
#include <fstream>
#include <string>
using namespace std;
ifstream fin("string.in");
ofstream fout("string.out");
#define cin fin
#define cout fout
string s(""),t("");
int ans=0,sl,start,tlen;
int main()
{
cin >> s >> t;
sl = s.length();
start = s[0];
tlen = t.length();
int temp=0;
for (int i=0; i<tlen; i++)
{
if (t[i] == s[temp]) temp++; //如果這一位匹配,那麼就繼續下一位判斷
else if (t[i] == start) temp=1; else temp=0; //如果有任意一位不匹配,從頭開始匹配。
if (temp == sl) {temp=0;ans++;} //如果完全匹配,將總數加一,temp從頭開始(繼續判斷)
}
cout << ans << endl;
return 0;
}