1. 程式人生 > >2015年南海區初中資訊學競賽試題解題報告

2015年南海區初中資訊學競賽試題解題報告

第一題 危險的實驗(dangerous

【題意分析】

n個化學物質放在桌子上,給出它們與附近化學物質的安全距離,請問最短需要多長的桌子。

【資料範圍】

20%的資料,1<=n<=20

50%的資料,1<=n<=100000

100%的資料,1<=n<=10000001<=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<=10000001<=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(祖先)進行比較,如果不相同就將broot改為aroot(反過來也可以,因為它是無向圖,只在乎是否為同一個連通塊)。(詳細程式請參考【參考程式】

講完前面,再回過頭來分析題目。題目有一點必須注意“一個連通塊是樹,當且僅當邊數比點數少1,這說明連通塊的數量並不等於樹的數量,應再根據上述條件進行判斷,也就是記錄邊數與點數。(點數初始值為1,邊數初始值為0,因為本身有一個點)合併時要注意,新的這個連通塊的邊數,應為原來兩個連通塊的邊數之和再加1,因為合併它們倆時還增加了一條邊,點數則不用加1

放到lemon裡一測試,問題就出來了,為什麼會超時?讓我們看回題目資料範圍n<=100000!!如果我們將這棵樹連線成線狀,最後一個點的查詢將執行100000while!!所以這一題還需要用到“壓縮路徑”。意思就是將很長的路徑縮短,直接連到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;
}

(對本程式的解釋:這是優化後的程式,將原來的記錄點數、邊數,改為了:如果xy本來就是一個連通塊,說明這已不是一棵樹(一個連通塊是樹,當且僅當邊數比點數少1),所以可以直接廢掉root,掃描時就不會將它算為樹。)

第四題 字串

【題意分析】

給出一個字串st,求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;
}