一维数组模拟堆
1.
View Code
Code
Code
Code

1 /如何手写一个堆?完全二叉树 5个操作 2 //1. 插入一个数 heap[ ++ size] = x; up(size); 3 //2. 求集合中的最小值 heap[1] 4 //3. 删除最小值 heap[1] = heap[size]; size -- ;down(1); 5 //4. 删除任意一个元素 heap[k] = heap[size]; size -- ;up(k); down(k); 6 //5. 修改任意一个元素 heap[k] = x; up(k); down(k); 7 #include <iostream> 8 9 using namespace std; 10 11 int const N = 100010; 12 13 //h[i] 表示第i个结点存储的值,i从1开始,2*i是左子节点,2*i + 1是右子节点 14 //size 既表示堆里存储的元素个数,又表示最后一个结点的下标 15 int h[N], siz; //堆有两个变量h[N],size; 怎么这里的size和文件里有冲突,只能改成siz了 16 17 void down(int u) 18 { 19 int t = u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u 20 if (u * 2 <= siz && h[u * 2] < h[t]) t = u * 2; // 左子节点存在并且小于当前结点,更新t的下标 21 if (u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右子节点存在并且小于当前结点,更新t的下标 22 if (t != u)//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值 23 { 24 swap(h[t], h[u]); 25 down(t); //交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小 26 } 27 } 28 29 int main() 30 { 31 int n, m; 32 cin >> n >> m; 33 for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]); 34 siz = n; //初始化size,表示堆里有n 个元素 35 36 for (int i = n / 2; i; i --) down(i); //把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉 37 38 while (m -- ) 39 { 40 printf("%d ", h[1]); 41 h[1] = h[siz]; 42 siz --; 43 down(1); 44 } 45 46 return 0; 47 }
2.acwing 839

1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N = 100010; 5 int n,m; 6 int h[N],siz; //模拟堆以及堆中有多少个元素 7 int ph[N],hp[N]; //第k个插入的点是哪个点(下标); 堆里面某个点是哪个插入的点 8 //ph从下标映射到堆里面去 ,hp从堆里映射到下标 9 10 void heap_swap(int a, int b) 11 { 12 swap(ph[hp[a]],ph[hp[b]]); 13 swap(hp[a],hp[b]); 14 swap(h[a],h[b]); 15 } 16 17 void down(int u) 18 { 19 int t = u; //t来表示三个点中的最小值的编号 20 if(u * 2 <= siz && h[u * 2] < h[t]) t = u * 2; //左儿子存在且左儿子小于t ,t等于左儿子 21 if(u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1; 22 if(u != t) //堆顶不是最小值 23 { 24 heap_swap(u,t); 25 26 down(t); 27 } 28 } 29 30 void up(int u) 31 { 32 while(u / 2 > 0 && h[u / 2] > h[u]) 33 { 34 heap_swap(u / 2,u); 35 u /= 2; 36 } 37 } 38 39 int main() 40 { 41 int n,m = 0; 42 scanf("%d", &n); 43 44 while (n -- ) 45 { 46 char op[10]; 47 int k ,x; 48 49 scanf("%s", op); 50 if(!strcmp(op,"I")) 51 { 52 scanf("%d", &x); 53 siz ++; 54 m ++; 55 ph[m] = siz,hp[siz] = m; 56 h[siz] = x; 57 up(siz); 58 } 59 else if(!strcmp(op,"PM")) 60 { 61 printf("%d\n",h[1]); 62 } 63 else if(!strcmp(op,"DM")) 64 { 65 heap_swap(1,siz); 66 siz --; 67 down(1); 68 } 69 else if(!strcmp(op,"D")) 70 { 71 scanf("%d", &k); 72 k = ph[k]; //找到堆中间的位置 73 heap_swap(k,siz); 74 siz --; 75 down(k),up(k); 76 77 } 78 else { 79 scanf("%d%d", &k, &x); 80 k = ph[k]; 81 h[k] = x; 82 down(k),up(k); 83 } 84 } 85 return 0; 86 }
3.合并果子P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=10000+10; 4 int n,heap[maxn],siz = 0; 5 void up(int u) //向上修复小根堆的过程 6 { 7 if( u > 1 &&h[u / 2] > h[u]) 8 { 9 swap(h[u],h[u / 2]); 10 u /= 2; 11 } 12 //cout << " " << u; 13 14 } 15 16 void insert(int x) //二叉堆插入,新元素放在堆底,向上调整 17 { 18 heap[++siz] = x; 19 up(siz); 20 } 21 void down(int u) //二叉小根堆向下调整 22 { 23 int t = u * 2; 24 while(t <= siz) 25 { //下面这句话是从左右儿子中选一个更小的做交换 26 if(t < siz && heap[t + 1] < heap[t]) t ++; 27 if(heap[t] < heap[u]) 28 { 29 swap(heap[t],heap[u]); 30 u = t; 31 t = u * 2; 32 } 33 else break; 34 } 35 } 36 void extract() //二叉堆删除堆顶 37 { 38 heap[1]=heap[siz --]; //将堆底移至堆顶,向下调整 39 down(1); 40 } 41 42 int gettop() //返回堆顶的值 43 { 44 return heap[1]; 45 } 46 int main() 47 { 48 cin>>n; 49 for(int i=1; i<=n; i++) 50 { 51 52 cin >> heap[i]; 53 } 54 siz = n; 55 for(int i = n / 2;i;i --) down(i); //建立二叉堆 56 57 long long ans = 0; //其实这里不会越界,但好像原题数据是3万 58 while(siz >= 2) //如果还可合并 59 { 60 int top1 = gettop(); //取出堆顶(堆中最小值)后删除堆顶 61 extract(); 62 int top2 = gettop(); //同上 63 extract(); 64 ans+=(top1+top2); 65 insert(top1+top2); //将两数之和加入二叉堆,重复运算 66 } 67 cout<<ans<<endl; 68 return 0; 69 }
4.P1878 舞蹈课 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
用结构体堆来维护 差值的最小值
每次弹出差值的最小值,并且作上标记,在下一次循环的时候删掉,更新堆顶
注意此题两个相邻的异性可以用异或的性质去简化代码,当我们重连 删除之后的链表(堆)的时候
,我们需要两个数组来存之前 两个堆顶端点的左右值,然后删除后让这两个值左右相连。

1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e5 + 10; 5 6 struct node 7 { 8 int l,r; 9 int v; 10 bool operator<(const node &x) const { 11 if(x.v == v) return x.l < l; 12 return x.v < v; 13 } 14 }; 15 priority_queue<node> q; 16 17 int n; 18 char ch; //字符 19 bool bg[N]; //用来判断字符串数组是否为异性,是为异性就可以入堆 20 int a[N]; 21 bool is[N]; //标记是否出现过 22 int l[N],r[N]; //表示两个端点的左右元素 23 vector<node> ans; 24 25 int main() 26 { 27 cin >> n; 28 for (int i = 1; i <= n; i ++ ) cin >> ch,bg[i] = (ch == 'B' ? 1 : 0); 29 for (int i = 1; i <= n; i ++ ) cin >> a[i]; 30 for (int i = 1; i <= n; i ++ ) l[i] = i - 1,r[i] = i + 1; 31 32 is[0] = is[n + 1] = 1; 33 for (int i = 2; i <= n; i ++ ) //如果相邻为异性,丢到堆中,从小到大进行排序 34 if(bg[i] ^ bg[i - 1]) q.push({i - 1,i,abs(a[i - 1] - a[i])}); 35 36 while(!q.empty()) 37 { //如果堆顶的元素两个端点之前出现过(已经被标记了),那么没用直接删掉 38 while(!q.empty() && (is[q.top().l] || is[q.top().r])) q.pop(); 39 if(q.empty()) break; //堆为空直接g; 40 node now = q.top(); //now 为堆顶的三个元素(a[i]元素下标) 41 q.pop(); //删掉并更新堆顶 42 int x = l[now.l],y = r[now.r]; // x为堆顶左端点的左边一个点,y同理 43 l[y] = x,r[x] = y; //让x,y互相相邻 44 if(bg[x] ^ bg[y]) q.push({x,y,abs(a[x] - a[y])}); //x,y互为异性丢到堆里面去 45 ans.push_back(now); //把堆顶的元素丢到 ans里面去 46 is[now.l] = 1,is[now.r] = 1; //标记堆顶两个端点出现过了 47 48 } 49 cout << ans.size() << endl; //有多少对刚才插入的堆顶元素 50 for (auto i : ans ) cout << i.l << ' ' << i.r << endl; //输出每一组的左右端点 51 return 0; 52 }