[JLU] 資料結構與演算法上機題解思路分享-課程設計第一次與第二次上機

陆爻齐發表於2024-07-03

前言

首先,請務必自己盡全力嘗試實現題目,直接看成品程式碼,思維就被拘束了,也很容易被查重。

這裡只是思路解析的部落格,程式碼倉庫在 JLU_Data_Structures_Record

希望你能在這裡找到你想要的:)

第一次上機


A 網路佈線

分數 50
作者 朱允剛
單位 吉林大學
2024年亞洲盃足球賽剛剛落下帷幕,賽前人們普遍預測:本屆比賽中日韓是最強的,冠軍也會從日韓中產生……隨著東道主卡達隊的奪冠,這一預言未能成真。

但我們這裡要研究的是另一個問題,亞洲盃賽期間需要保證運動員公寓網路暢通,以使運動員都能正常上網。

假定公寓樓內有n個房間,編號為0…n−1,每個房間都需要網路連線。房間 i 有網路,當且僅當滿足如下2個條件之一:

(1)房間 i 安裝了路由器(成本為 ri>0)

(2)房間 i 和房間 j 有網線連線且房間 j 有網路(在房間 i 和房間 j 之間佈置網線的成本為 fij>0)

假定你是賽事組委會的網路工程師,請編寫程式設計一個網路佈線方案(哪些房間安裝路由器,哪些房間之間佈置網線),使得所有房間都有網路,且總成本最小。

例如下圖包含7個房間和10個可能的連線,安裝路由器的成本為括號內數字,房間之間佈置網線的成本為邊的權值。其解決方案為右下圖,即在房間1和4安裝路由器,並進行圖中的網線佈置。總成本為120。

img.png

輸入格式:
輸入第一行為兩個正整數n和e;n為房間數,不超過600;e為可能的連線數,不超過2×10^5。接下來一行為n個空格間隔的正整數,第i個整數(i≥0)表示在房間i安裝路由器的成本。接下來e行,每行為3個非負整數i、j、f,表示在房間i和房間j之間佈置網線的成本為f。

輸出格式:
輸出為一個整數,表示最優網路佈線方案的成本。

輸入樣例:
7 10
60 10 35 55 40 70 70
0 1 20
0 4 75
0 3 45
1 3 50
1 2 15
2 6 5
5 6 45
4 5 5
3 5 25
3 6 65

輸出樣例:
120

提示:
可引入一個虛擬頂點,將該頂點與其他所有頂點用邊相連,邊權等於那些頂點的權值。進而形成一個新圖,對新圖求最小支撐樹。注意本題頂點編號從0開始。
image.png
image.png

程式碼長度限制
16 KB
時間限制
60 ms
記憶體限制
64 MB
棧限制
8


思路都在提示裡,唯一能補充的就是構造最小支撐樹的演算法,有Kruskal或Prim,本質就是在不產生迴路的情況下,從最小邊或某一點為角度切入,不斷選取權值最小的邊,並透過棧或佇列儲存下次可能檢查的邊或點


B 社交網路

分數 50
作者 朱允剛
單位 吉林大學
可以將n個QQ使用者間的好友關係建模為一個包含n個頂點的無向圖,頂點編號為1至n,每個頂點對應一個使用者,若2個使用者i和j是QQ好友,則在頂點i和j之間連線一條邊,並根據使用者間的親密度對該邊附以一個權值c
ij。在該圖中,可以利用兩個頂點間的最短路徑長度衡量兩個使用者的關係密切程度,也可以利用經過一個頂點的最短路徑數目來衡量一個使用者在關係網路中的影響力,具體地,我們定義使用者k在QQ關係網路中的“影響力”為:

333.png

其中Nij 為頂點i到j的最短路徑數目,Nijk 為頂點i到j的所有最短路徑中經過頂點k的最短路徑數目(上述二值可能超出int型範圍,請使用long long型別)。Dij 表示i到j的最短路徑長度。

現給定一個如上描述的無向圖,請編寫程式,計算每個頂點的“影響力”,假定給定的圖是連通的。

輸入格式:
輸入第一行為兩個正整數n和e,分別表示圖的頂點數和邊數,接下來e行表示每條邊的資訊,每行為3個正整數a、b、c,其中a和b表示該邊的端點編號,c表示權值。各邊並非按端點編號順序排列。

n≤100,e≤5000,c≤1000,任意兩點間的最短路徑數目≤10
10

輸出格式:
輸出為n行,每行一個實數,精確到小數點後3位,第i行為頂點i的影響力。

輸入樣例:
4 4
3 2 6
4 3 1
1 3 9
4 1 1

輸出樣例:
0.000
0.000
30.000
20.000

解釋:
對於頂點1:邊2-3、3-4、2-4的最短路徑均不經過頂點1,故頂點1的影響力為0.

對於頂點3:
頂點1到2的最短路徑共1條,長度為8,經過點3,頂點2到4的最短路徑共1條,長度為7,經過點3,頂點1到4的最短路徑共1條,但不經過點3。
故f(3)=D 12 ∗1+D 24 ∗1+D 14 ∗0+D 21 ∗1+D 42 ∗1+D 41 ∗0=8+7+0+8+7+0=30.000

提示:
若頂點a到頂點b有x條路徑,點b到點c有y條路徑,則a經過b到達c的路徑有x*y條。

程式碼長度限制
16 KB
時間限制
50 ms
記憶體限制
64 MB
棧限制
8192 KB


該程式的本質要求需要所有點到其餘點的最短路徑,以計算得出所謂的“影響力”。那麼就有迪傑斯特拉演算法和Floyd演算法給選擇。通常來說,點少邊多適合Floyd,點多邊少適合迪傑斯特拉演算法。

陸爻齊在本題採用Floyd演算法,得出所有的最短路徑,然後遍歷所有路徑來統計影響力即可。


第二次上機


A 手撕STL sort(基礎版)

分數 40
作者 朱允剛
單位 吉林大學
C++ STL是Standard Template Library的簡稱,即標準模板庫。簡單來說,STL將常用的資料結構與演算法進行了封裝,使用者需要時可以直接呼叫,不用重新開發。排序演算法sort( )是STL包含的一個重要演算法。

STL中的sort()函式基於快速排序演算法實現,眾所眾知,快速排序是目前已知平均情況下最快的排序演算法,被IEEE評選為20世紀十大演算法之一,但其最壞情況下時間複雜度會退化為O(n 2 )。STL中的sort()對傳統快速排序做了巧妙的改進,使其最壞情況下時間複雜度也能維持在O(nlogn),它是如何實現的呢?

(1)快速排序演算法最壞情況下時間複雜度退化為O(n 2 )的主要原因是,每次劃分(Partition)操作時,都分在子陣列的最邊上,導致遞迴深度惡化為O(n)層。而STL的sort()在Partition操作有惡化傾向時,能夠自我偵測,轉而改為堆排序,使效率維持在堆排序的O(nlogn)。其具體方法是:偵測快速排序的遞迴深度,當遞迴深度達到⌊2log 2 n⌋=O(logn)層時,強行停止遞迴,轉而對當前處理的子陣列進行堆排序。

(2)此外,傳統的快速排序在資料量很小時,為極小的子陣列產生許多的遞迴呼叫,得不償失。為此,STL的sort()進行了最佳化,在小資料量的情況下改用插入排序。具體做法是:當遞迴處理的子陣列長度(子陣列包含的元素個數)小於等於某個閾值threshold 時,停止處理並退出本層遞迴,使當前子陣列停留在“接近排序但尚未完成”的狀態,最後待所有遞迴都退出後,再對整個序列進行一次插入排序(注意不是對當前處理的子陣列進行插入排序,而是在快速排序的所有遞迴完全退出後,對整個陣列統一進行一次插入排序)。實驗表明,此種策略有著良好的效率,因為插入排序在面對“接近有序”的序列時擁有良好的效能。

在本題中,請你按照上述思路,自己實現STL的sort()函式。

備註:Partition操作選取第1個元素作為基準元素。Partition操作的不同實現可能導致不同的輸出結果,為保證輸出結果唯一,該操作的實現請以教材為準,即Hoare提出快速排序演算法時最早給出的Partition實現

函式介面定義:
void sort(int *R, int n);
功能為對整數R[1]…R[n]遞增排序。

裁判測試程式樣例:


#include<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
int threshold;

//請在這裡補充你的程式碼,即你所實現的sort函式

int main()
{
    int n,i;
    int a[50010];
    scanf("%d %d", &n, &threshold);
    for (i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    
    sort(a,n);
    
    printf("Final:");
    for (i = 1; i <= n; i++)
        printf("%d ",a[i]);
    printf("\n");
    return 0;
}

備註:提交程式碼時,只需提交sort函式以及你自定義的其他函式,不用提交#include或者main函式等內容。

輸入格式:
輸入第一行為2個正整數n和threshold,n為待排序的元素個數,不超過50000,threshold為改用插入排序的閾值,不超過20,含義如上所述。第二行為n個空格間隔的整數。本題中讀入資料的操作無需你來實現,而由框架程式完成。

輸出格式:
輸出第一行為以depth_limit:開頭的整數,表示轉為堆排序的遞迴深度,即⌊2log 2 n⌋。從第二行開始,輸出對某子陣列轉為堆排序後,該子陣列初始建堆的結果,每個元素後一個空格,每個堆佔一行,以Heap:開頭。注意,可能不止一個堆。接下來下一行,輸出n個整數,每個整數後一個空格,為快速排序所有遞迴退出後,插入排序執行前的陣列元素,以Intermediate:開頭。最後一行為n整數,每個整數後一個空格,表示排序後的陣列,以Final:開頭(最後一行由框架程式完成,無需你來輸出)。

輸入樣例1:
10 2
10 9 8 7 6 5 4 3 2 1

輸出樣例1:
depth_limit:6
Heap:7 6 5 4
Intermediate:1 2 3 4 5 6 7 8 9 10
Final:1 2 3 4 5 6 7 8 9 10

輸入樣例2:
60 2
66 61 92 22 50 80 39 2 25 60 49 17 37 19 24 57 40 82 11 52 45 0 33 78 32 25 19 42 92 50 39 87 74 87 56 79 63 63 80 83 50 3 87 2 91 77 87 10 59 23 25 6 49 85 9 95 60 16 28 1

輸出樣例2:
depth_limit:11
Heap:24 19 23 19 17 22
Intermediate:1 0 2 2 3 6 10 9 11 16 17 19 19 22 23 24 25 25 25 28 32 33 37 39 39 42 40 45 49 49 50 50 50 52 56 57 59 60 60 61 63 63 66 77 74 78 79 80 80 82 83 85 87 87 87 87 91 92 92 95
Final:0 1 2 2 3 6 9 10 11 16 17 19 19 22 23 24 25 25 25 28 32 33 37 39 39 40 42 45 49 49 50 50 50 52 56 57 59 60 60 61 63 63 66 74 77 78 79 80 80 82 83 85 87 87 87 87 91 92 92 95

程式碼長度限制
16 KB
時間限制
100 ms
記憶體限制
64 MB


這題思維上並無難點,堆排序,插入排序,快速排序都是基礎要掌握的排序方式,但由於要檢查一些中間輸出,會限定得很死。

簡要說下陸爻齊程式的大致思路好了,排序大體分兩塊,快排和插排,畢竟堆排是在快排中可能進入的,插排是快排完成後才開始的。快排的每層都會檢測深度是否足夠轉化為堆排或是否要退出等待插排。至於快排的細節,如題所說,以教材相關內容為準吧,是在不行再看看老師ppt


B 排序

分數 10
作者 朱允剛
單位 吉林大學
請編寫程式對不超過50000個整數遞增排序。
備註:本題不允許使用STL sort()或qsort()等現成的排序庫函式。

輸入格式:
輸入第一行一個正整數n,表示待排序的元素個數。第二行為n個整數,表示待排序的元素。n不超過50000。

輸出格式:
輸出為一行,表示排序結果,每個整數後一個空格。

輸入樣例:
5
5 4 3 2 1

輸出樣例:
1 2 3 4 5

程式碼長度限制
16 KB
時間限制
5000 ms
記憶體限制
64 MB
棧限制
8192 KB


時間上限制很鬆,冒泡應該也行,其實把上道題寫好的排序拉下來也行。


C 智慧提示

分數 30
作者 朱允剛
單位 吉林大學
百度、谷歌等搜尋引擎,以及輸入法等各種軟體通常包含這樣一個功能,當使用者在輸入框輸入資訊時,軟體會提供一種“智慧提示”。對使用者所輸入的資訊,自動補全、列出可能的完整單詞,提示給使用者,以方便使用者輸入,提升使用者體驗。

pic.jpg

這個功能是如何實現的呢?一種典型的實現方式是,在系統後臺維護一個字典,當使用者輸入字元時,在字典中查詢以使用者當前輸入的字串為字首的全部單詞,選取其中歷史使用率最高的若干單詞作為候選詞,建議給使用者。

請編寫程式實現上述功能。

備註:這裡約定一個字串不能稱為自己的字首。若使用者輸入的字串恰好是字典中的一個單詞,則該單詞不必向使用者建議。

輸入格式:
輸入第一行為3個正整數n、m、k。n為字典中單詞個數。m為使用者查詢數,即使用者輸入的單詞個數。對於使用者輸入的每個字串,程式需要返回字典中以該字串為字首的、歷史使用頻率最高的k個單詞。接下來n行,表示字典資訊,每行為1個整數和1個字串,整數表示單詞的歷史使用頻率,字串表示單詞,請注意,單詞包含的每個字元為a-z的小寫字母或0-9的數字,即數字也可能構成字典中的單詞。字典內的單詞並非按使用頻率有序存放。接下來m行,表示使用者的查詢,每行為一個a-z的小寫字母或0-9的數字組成的字串,表示使用者的查詢。另外請注意,由於字典往往是在使用者歷史資料的基礎上加工而得,所以字典中可能出現重複單詞,若某個單詞在字典中出現多次,則其歷史使用頻率以最高者為準。 (n ≤ 10000, m ≤ 20000, k ≤ 10, 每個單詞長度不超過20,單詞歷史使用頻率小於2
31
)

輸出格式:
對於使用者輸入的每個字串,按使用頻率降序輸出字典中以該字串為字首的、歷史使用頻率最高的k個單詞,每個佔1行。若多個單詞歷史使用頻率相同,則字典序靠前的單詞排名靠前。若單詞中包含數字,則字典序以ACSII碼判定,即0<1<2<…<9<a<b<c<…<z。若字典中滿足輸出條件的單詞個數大於0小於k,則有多少就輸出多少個。若字典中沒有滿足輸出條件的單詞,則輸出“no suggestion”。針對使用者每個查詢所輸出的資訊,用空行間隔。

輸入樣例:
20 3 4
1827187200 the
1595609600 to
1107331800 that
401542500 this
334039800 they
282026500 their
250991700 them
196118888 these
150877900 than
144968100 time
125563600 then
109336600 two
196120000 there
87862100 those
79292500 through
75885600 the
71578000 think
67462300 2
65648600 tx356
57087700 though
th
xxx
the
輸出樣例:
the
that
this
they

no suggestion

they
their
them
there

程式碼長度限制
16 KB
時間限制
800 ms
記憶體限制
64 MB
棧限制
8192 KB

D 智慧提示(時間限制更嚴格)

分數 20
作者 朱允剛
單位 吉林大學
題目內容與前一題完全一致,但時間限制更為嚴格,若上題程式效率足夠高,可直接將程式碼提交至本題。

備註:本題的測試點其實就是上一題的測試點4、5。如果上一題測試點4、5能在60ms內透過,本題就可以透過了。

輸入格式:
與前一題相同。

輸出格式:
與前一題相同。

輸入樣例:
與前一題相同。
輸出樣例:
與前一題相同。
程式碼長度限制
16 KB
時間限制
60 ms
記憶體限制
64 MB
棧限制
8192 KB


CD兩題一起講,畢竟D就是C更嚴格時間限制罷了。

該題實際上是想學生建立字典樹,並做一點改良,先從字典樹簡單說說好了,就如同此前的樹一般,只不過樹裡面存的是單個字母,從樹的根,也就是最“上面”的頂點往“下”走到葉子節點,途徑字母組成單詞,這便是字典樹。

只要建立了字典樹,並輸出指定節點的所有葉子節點,就算是解決了C題。

那麼如何解決D題呢?兩個思路

其一是減少輸入輸出所消耗的時間,常用C++打OI的同學可能疑惑,已經給cin、cout加速了,還要繼續最佳化嗎?要的,陸爻齊最後用了scanf和printf才過,所以要把所有cout換printf和cin換scanf;

其二是最佳化中間步驟,其實我們細想下,在字典樹的建立,查詢對應點和搜尋點對應所有葉子節點哪個最複雜、很可能消耗最多時間呢?當然是搜尋葉子節點,畢竟同樣的單詞字首,可對應的單詞幾乎無窮無盡。但其實,如果在建立字典樹的時候,我們就把一個單詞放進節點裡,最後直接從該節點讀取詞,豈不美哉

有同學讀完並實踐完上述兩點,發現還不夠,就來說:陸爻齊,你騙人,還不行。確實,還差點,最後一點在於每個節點所儲存的詞可能太多了,要找出10個歷史頻率最高,同歷史頻率,字典序最低的詞,還需要在每次把詞放節點裡時做下排序。這裡可以用sort排序配合自己寫的比較函式,這部分內容不會建議google一下或者bing一下。


小結

資料結構課程設計的前兩次上機主要考察了圖、排序和字典樹,字典樹的這兩題很有趣,除了比較費時間、費精力、費腦子外挺好的:)

相關文章