基本算法之贪心算法
貪心算法
- 防嗮
- 難點:
- 技巧:
- 畜欄預定
- 難點
- 技巧
- 雷達設備
- 給樹染色
- 如何合并節點
- 為什么可以這樣簡化、
防嗮
題目鏈接
解題思路:
我們首先將奶??梢猿惺艿淖钚≈?遞減排序,也就是降序排列,然后將防曬霜固定的值,遞減排序,還是降序排列.
對于每一個頭奶牛而言,當然是要選擇目前來說滿足條件的最差的防曬霜,什么最差的定義,就是選擇滿足奶牛條件的SPF最大的那一瓶防曬霜.
注意:降序排序,保證對于每一頭牛而言,它用的是,可以使用的最差的防曬霜,因為值越小的防曬霜,有可能讓更多的牛使用.而升序排列,就恰好反了. 注意:如果一頭牛沒有合適的防嗮霜給它了就不用管他了。
難點:
- 如何高效的找到小于等于一個值的最大值
- 如何動態刪除一個元素。(用完了要刪除)
- 區間排序
技巧:
- 以上兩個操作是平衡樹的兩個基本操作因此我們可以使用平衡樹來解決。在STL中有map 和 set
- 對于區間來說我們可以使用pair ,因為pair 內部安排了默認的排序方式:以first 為關鍵字 升序排列。
- 設置哨兵是為了讓查找不為空不返回空。
拓展知識點:
增廣路徑: 預先選一些匹配的邊,從任一個不在匹配的點,先沿著非匹配邊,再沿著匹配邊走,。。。,交替,最后找到一個不在匹配里的點。(就找到了一個增廣路徑)。 如果一個匹配沒有增廣路徑,則達到了最大匹配。
代碼:
#include<stdio.h> #include<map> #include<algorithm> using namespace std;typedef pair<int ,int > pll; const int N = 2510;int n ,m; pll cows[N];int main(){scanf("%d%d",&n,&m);for(int i=0;i<n;++i)scanf("%d%d",&cows[i].first,&cows[i].second);sort(cows,cows+n);map<int,int >spfs;for(int i=0;i<m;++i){int x,cover;scanf("%d%d",&x,&cover);spfs[x] += cover;}int ans = 0;spfs[0] = spfs[1001] = n; // 哨兵,避免使用 查找函數時出錯for(int i = n-1;i >= 0;--i){auto cow = cows[i];// 大于的最小的值auto it = spfs.upper_bound(cow.second); // 以區間的右邊找 ,難點一it--; // 找到小于等于的最大值if(it->first >= cow.first && it->first <= cow.second){ans ++;if(-- it->second == 0)spfs.erase(it); // 難點二}}printf("%d\n",ans);return 0; }畜欄預定
題目鏈接
解題思路:
- 將所有牛按開始吃草的時間排序;
- 用小根堆維護當前所有畜欄的最后一頭牛的吃草結束時間;
- 如果當前的??梢园才旁诙秧數男髾谥?#xff0c;則將其安排進去,否則就新建一個畜欄;
難點
- 對于每頭牛我們既要記錄區間,還要記錄編號。
- 小根堆(即圍欄)的更新操作。
- 記錄每頭牛進入的圍欄的編號。
技巧
- 對于第一個難點我們可以用 pair<pair<int,int>,int>的方式來存儲。
- 對于第二個難點,我們小根堆里的元素類型也是 pair 這樣我們記錄兩個元素一個了一頭牛吃完草結束的時間,另一個是圍欄的編號。所以在更新操作的時候我們把頂部的圍欄拿出來修改得時候先把其刪除,在更新結束時間的時候,把原來圍欄的編號記錄下。
- 因為我們每頭牛都有一個編號,而圍欄也有,所以我們用一個數組來存儲這兩個對應的關系。其中牛的編號就是 0~n-1 ,圍欄編號就是創建時小根堆中圍欄元素個數。
代碼:
#include<stdio.h> #include<vector> #include<algorithm> #include<queue>using namespace std;typedef pair<int ,int> PII ;pair <PII,int> cows[50010]; int n; int id[50010];int main (){scanf("%d",&n);for(int i = 0;i<n;++i){// 難點一就是如何存這么多元素scanf("%d%d",&cows[i].first.first,&cows[i].first.second);cows[i].second = i;}sort(cows,cows+n);priority_queue<PII,vector<PII>,greater<PII>>heap;for(int i=0;i<n;++i){auto cow = cows[i].first;// 新建一個圍欄if(heap.empty() || heap.top().first >= cow.first){PII stall = {cow.second ,heap.size() + 1 };id[cows[i].second] = stall.second;heap.push(stall);}else{// 難點二,更新圍欄PII stall = heap.top();heap.pop();stall.first = cow.second;id[cows[i].second] = stall.second;heap.push(stall);}}printf("%d\n",heap.size());for(int i = 0;i <n ;++i)printf("%d\n",id[i]);return 0; }雷達設備
題目鏈接
解題思路
如下圖所示,對于任意一個小島 (x,y)(x,y),我們都可以在海岸線上求出能覆蓋該小島的建造雷達的區間 [a,b]。
由勾股定理,將所有小島轉化成區間后,問題轉化為:給定 n 個區間,在 x軸上選擇盡量少的點,使得所有區間至少包含一個點。
算法步驟:
如果當前區間包含最后一個選擇的點,則直接跳過;
如果當前區間不包含最后一個選擇的點,則在當前區間的右端點的位置選 一個新的點;
代碼:
#include<stdio.h> #include<algorithm> #include<math.h> using namespace std;typedef pair<double,double> PDD; const double eps = 1e-6 , INF = 1e10; int n,R; PDD segs[1010]; bool success ; int main(){success = 1;scanf("%d%d",&n,&R);for(int i = 0;i< n;++i){int x,y;scanf("%d%d",&x,&y);if(y > R){success = 0;break;}double len = sqrt(R * R - y * y );segs[i] = {x + len,x - len};}if(!success) puts("-1");else{sort(segs,segs + n);int ans = 0;double pos = - INF;for(int i = 0;i<n;++i){auto seg = segs[i];if(seg.second - pos > eps){ans ++ ;pos = seg.first;}}printf("%d\n",ans);}return 0; }給樹染色
題目鏈接
解題思路
每次找出當前權值最大的非根節點,將其染色順序排在緊隨父節點之后的位置,然后將該點合并進父節點中,更新父節點的權值。直到將所有點都合并進根節點為止。
如果直接按上述算法做的話,最終的分值不太容易計算,我們可以在將點合并的時候,實時更新當前的權值和:
- 最初所有點各自為一組,總分值是 S=∑i=1nai?1S=\sum_{i=1}^n a_i?1S=∑i=1n?ai??1;
- 接下來每次會將兩組點合并,將其中一組點接在另一組點的后面。比如兩組點分別是 xix_ixi?和 yiy_iyi?,我們將 yiy_iyi?接在 xix_ixi? 之后,則 yiy_iyi?中每個點所乘的系數均會增加一個相同的偏移量,這個偏移量就是 xix_ixi?中點的個數,假設是 k,則合并之后,總的權值直接加上 k?∑yik?∑y_ik?∑yi?即可;
難點:
- 如何合并節點
- 為什么可以這樣簡化
如何合并節點
因此可以看到只要將父節點的關系轉化以下就好。
為什么可以這樣簡化、
我們看把 y 的一堆節點放到x 后邊 ,x 的值是不會變得,而 y 每一個節點的值相當于 乘上 x 中 的節點個數。
代碼:
#include<stdio.h> #include<algorithm>using namespace std;const int N = 1010; struct node{int father,size,sum;double avg; }nodes[N];int n ,root; // 根據 平均值,找到最大的點 int find(){int pos = -1 ;double maxx = 0;for(int i= 1;i <= n ;++i){if( i != root && maxx < nodes[i].avg){maxx = nodes[i].avg;pos = i;}}return pos; }int main(){int ans = 0;scanf("%d%d",&n,&root);for(int i = 1;i <=n; ++i){node &nd = nodes[i];scanf("%d",&nd.sum);nd.size = 1 ;nd.avg = nd.sum;ans += nd.sum; // }// 建立關系for(int i = 0, a, b;i< n-1;++i ){scanf("%d%d",&a,&b);nodes[b].father = a;}// 遍歷 n - 1 次for(int i = 0;i< n -1; ++ i){int ver = find();int fa = nodes[ver].father;// 當前節點的 總和 乘上 父節點 的大小ans += nodes[ver].sum * nodes[fa].size;// 用完這后將該節點刪除,//因為 找節點是用find () 函數 依據avg 值找,所以置為 -1 就不會用到了nodes[ver].avg = -1;// 難點 :合并節點,遍歷所有節點。// 將原本是其 子節點 的直接連上其父節點即可for(int j = 1 ; j <= n ;++j)if(nodes[j].father == ver)nodes[j].father = fa;// 難點三 : 更新父節點的信息nodes[fa].size += nodes[ver].size ;nodes[fa].sum += nodes[ver].sum;nodes[fa].avg = (double)nodes[fa].sum / nodes[fa].size;}printf("%d\n",ans);return 0; }寫在最后,感謝y總的講解。
總結