最近在復習小根堆,看了好多博客,一些思想記錄一下。
早上自己團隊在比賽的時候,第一道題爆零,老師講是用小根堆解決,所以好好復習了一下小根堆;
首先,小根堆其實就是二叉樹。當然,最出名的是一個叫做堆排序的東東,它的時間復雜度為O(nlogn)。足夠的小吧,此外它還有一個別名叫做二叉樹排序。
贈送團隊第一題的鏈接:
劍與魔法
唔,博主寫這題的時候的直接想法是DFS,當然這樣是解決不了的,雖然博主不知道為什么解決不了,但是還是將思路留在這里:
我是這么想的,首先輸出“-1”的條件是,事件中所有戰役事件加起來並沒有達到穿越回去事件的RP值,那么老師不僅拿不到金幣還回不到過去,這個時候就應該輸出“-1”,
然后,就是成立的條件一個“else”,那么老師可以拿到的金幣就是各個戰役的RP++,首先,因為題面要求老師必須在最后一個返回事件返回,所以無論前面有多少個事件都不能觸發,
然而根據題意,穿越回去事件的觸發是被動的,只有參與的戰役數達到RP時才能穿越,所以搜索所有的戰役事件,並且所進行的戰役不能超過前面所有的“EOF”事件的RP值,
這就導致了我們要將所有的嘗試值存儲下來,然后進行對比“Max(dfs(1),dfs(2))”,這樣就可以求得每個“EOF”事件之前所獲得的金幣最大值。
(當然博主不知道為啥一直調試不出來,希望能有位大佬幫助一下蒟蒻(逃)
其次,使用優先隊列做法(這個是我們團隊的大佬寫的,不敢copy過來,但是可以偷偷看一下他的代碼,恕我吐槽一句他的代碼風真的很小清新)
運用優先隊列存儲“RP”值與金幣數,運用sum進行判斷,優先隊列的頭頂元素存為最大值,這樣子“ans”所得的結果就是最大值。
最后,是運用小根堆來解題,當然我並不是很清楚思路,似乎是使用堆排序,把最大值放到樹的前端,然后調用來着(弱弱的我,打算溜走)
給上題目里的標程,希望對大家的理解有幫助:
#include<cstdio> #include<algorithm> using namespace std; const int N = 201013; int c[N],d[N],h[N]; int tt,n,cnt; char op[N]; bool cmph(const int i, const int j) { return c[i] > c[j]; } int main() { //freopen("dragons.in","r",stdin); //freopen("dragons.out","w",stdout); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%s%d", op + i, c + i); tt = 0; for (int i = 1; i < n; ++i) if (op[i] == 'e') { while (tt && c[d[tt]] >= c[i]) --tt; d[++tt] = i; } d[tt + 1] = n; tt = 0; for (int i = 1, j = 1; i < n; ++i) if (op[i] == 'c') { h[++tt] = i; push_heap(h + 1, h + tt + 1, cmph); } else if (i == d[j]) { while (tt >= c[i]) pop_heap(h + 1, h + tt-- + 1, cmph); ++j; } if (tt >= c[n]) { int ret = 0; for (int i = 1; i <= tt; ++i) ret += c[h[i]]; printf("%d\n", ret); } else puts("-1"); return 0; }
好啦,我還是總結個人的理解吧;
我的理解學習來自這幾個博客主:ganggexiongqi,山代王,kiu000
堆的定義:
n個關鍵字序列L[1…n]稱為堆,當且僅當該序列滿足:
1. L(i)<=L(2i)且L(i)<=L(2i+1)
2. L(i)>=L(2i)且L(i)>=L(2i+1)
滿足第一個條件的成為小根堆(即每個結點值小於它的左右孩子結點值),滿足第二個添加的成為大根堆(即每個結點值大於它的左右孩子結點值)。
關於小根堆的創建:
唔,應該是類似於創建樹。
1. 復制堆數組
2. 找到最初的調整位置,即找到最后一個分支結點
3.1自底向上逐步擴大形成堆
3.2 向前交換一個分支結點
小根堆的插入:
1. 將待插入元素插入已建成堆的最后面
2. 沿着出入位置所在的分支逐步向上調整
小根堆的刪除:
1. 將堆頂元素刪除
2. 將數組中最后一個元素放到堆頂
堆的操作:
heapify(heap,i):若節點heap[i]左子樹和右子樹都滿足最小堆的性質,而heap[i]節點不滿足最小堆性質,
即heap[i]>heap[i*2]或者heap[i]>heap[2*i+1],則操作heapify(heap,i)調整heap[i]的位置來保持堆的性質。這是堆的基本操作。
heapinsert(heap,val):往堆里面插入值val,新增節點heap[n+1]=val,並比較新增節點和其父節點大小,不斷調整新增節點的位置,保持最小堆的性質。
heappop(heap):彈出堆頂元素,並令heap[0]=heap[n],堆大小減一,之后執行heapify(heap,0)來維持堆的性質。
ganggexiongqi那里有一幅圖有助於理解:
大佬代碼:
public static int[] heapSort(int[] A, int n, int k) { if(A == null || A.length == 0 || n < k){ return null; } int[] heap = new int[k]; for(int i = 0; i < k; i++){ heap[i] = A[i]; } buildMinHeap(heap,k);//先建立一個小堆 for(int i = k; i < n; i++){ A[i-k] = heap[0];//難處堆頂最小元素 heap[0] = A[i]; adjust(heap,0,k); } for(int i = n-k;i < n; i++){ A[i] = heap[0]; heap[0] = heap[k-1]; adjust(heap,0,--k);//縮小調整的范圍 } return A; } //建立一個小根堆 private static void buildMinHeap(int[] a, int len) { for(int i = (len-1) / 2; i >= 0; i--){ adjust(a,i,len); } } //往下調整,使得重新復合小根堆的性質 private static void adjust(int[] a, int k, int len) { int temp = a[k]; for(int i = 2 * k + 1; i < len; i = i * 2 + 1){ if(i < len - 1 && a[i+1] < a[i])//如果有右孩子結點,並且右孩子結點值小於左海子結點值 i++;//取K較小的子節點的下標 if(temp <= a[i]) break;//篩選結束,不用往下調整了 else{//需要往下調整 a[k] = a[i]; k = i;//k指向需要調整的新的結點 } } a[k] = temp;//本趟需要調整的值最終放到最后一個需要調整的結點處 }
堆排序和優先隊列:
由上述堆的基本操作基本可以實現堆排序和優先隊列。
堆排序:1,在線算法,不斷heapinsert接受所有的數據后,heappop輸出所有數據
2,離線算法,利用heapify操作創建最小堆,heappop輸出所有數據
優先隊列:heapinsert插入優先隊列,heappop彈出優先隊列