「数据结构」第1章 二叉堆课堂过关
「数据结构」第1章 二叉堆课堂过关
堆-结构体模板
struct Heap {
#define max_(_ , __) ((_) < (__) ? (_) : (__))
int siz;
int a[nn * 2];
bool ty;
inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;}
void clear() {
siz = 0 , ty = 0;
memset(a , 0 , sizeof(a));
}
inline void push(int dat) {
a[++siz] = dat;
int p = siz;
while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
swap_(p >> 1 , p) , p >>= 1;
}
inline void pop() {
swap_(1 , siz); --siz;
int p = 1 , tmp;
//这里可读性比较差,注意向下调节时当前节点与两个子节点比较
while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
swap_(tmp , p) , p = tmp;
}
inline int top() {
return siz == 0 ? 0 : a[1];
}
inline bool empty(){return siz == 0;}
}h;
A. 【例题1】合并果子
题目
代码
手工堆
#include <iostream>
#include <cstdio>
#include <cstring>
#define nn 100010
using namespace std;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return re;
}
struct Heap {
#define max_(_ , __) ((_) < (__) ? (_) : (__))
int siz;
int a[nn * 2];
bool ty;
inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;}
void clear() {
siz = 0 , ty = 0;
memset(a , 0 , sizeof(a));
}
inline void push(int dat) {
a[++siz] = dat;
int p = siz;
while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
swap_(p >> 1 , p) , p >>= 1;
}
inline void pop() {
swap_(1 , siz); --siz;
int p = 1 , tmp;
//这里可读性比较差,注意向下调节时当前节点与两个子节点比较
while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
swap_(tmp , p) , p = tmp;
}
inline int top() {
return siz == 0 ? 0 : a[1];
}
inline bool empty(){return siz == 0;}
}h;
int n;
int main() {
h.clear();
n = read();
for(int i = 1 ; i <= n ; i++)
h.push(read());
int ans = 0;
while(h.siz > 1) {
int add = 0;
add += h.top(); h.pop();
add += h.top(); h.pop();
ans += add;
h.push(add);
}
cout << ans;
return 0;
}
STL优先队列
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define nn 100010
using namespace std;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return re;
}
priority_queue <int> h;
int n;
int main() {
n = read();
for(int i = 1 ; i <= n ; i++)
h.push(-read());//默认大根堆
int ans = 0;
while(h.size() > 1) {
int add = 0;
add += h.top(); h.pop();
add += h.top(); h.pop();
ans += add;
h.push(add);
}
cout << -ans;
return 0;
}
B. 【例题2】序列合并
题目
思路
\(b\)数组从小到大排序的
显然,对于同一个\(i\),若\(j\)越大,\(a_i+b_j\)越大.题目要求的是前\(n\)小,所以我们设一个\(p\)数组,初始值全部为1,将\(a_i+b_{p_i}(1\le i\le n)\)和\(i\)捆绑起来压入小根堆,进行\(n\)次循环,每次从堆中取出最小的\(a_i+b_{p_i}\),\(p_i=p_i+1\),若\(p_i\le n\),将\(a_i+b_{p_i}\)压入小根堆(注意\(p_i\))已发生变化
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 100010
using namespace std;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return re;
}
struct Heap {
#define max_(_ , __) ((_) < (__) ? (_) : (__))
int siz;
int a[nn * 2];
int dat[nn * 2];
bool ty;
inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp; tmp = dat[x] ; dat[x] = dat[y] ; dat[y] = tmp;}
void clear() {
siz = 0 , ty = 0;
memset(a , 0 , sizeof(a));
memset(dat , 0 , sizeof(dat));
}
inline void push(int a_ , int dat_) {
a[++siz] = a_;
dat[siz] = dat_;
int p = siz;
while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
swap_(p >> 1 , p) , p >>= 1;
}
inline void pop() {
swap_(1 , siz); --siz;
int p = 1 , tmp;
while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
swap_(tmp , p) , p = tmp;
}
inline int top() {
return siz == 0 ? 0 : a[1];
}
inline int top_dat() {
return siz == 0 ? 0 : dat[1];
}
inline bool empty(){return siz == 0;}
}h;
int n;
int a[nn];
int b[nn];
int p[nn];
int main() {
h.clear();
n = read();
for(int i = 1 ; i <= n ; i++)
a[i] = read();
for(int i = 1 ; i <= n ; i++)
b[i] = read();
for(int i = 1 ; i <= n ; i++) {
p[i] = 1;
h.push(a[i] + b[p[i]] , i);
}
for(int i = 1 ; i <= n ; i++) {
printf("%d " , h.top());
int k = h.top_dat();
h.pop();
p[k]++;
if(p[k] <= n)
h.push(a[k] + b[p[k]] , k);
}
return 0;
}
C. 【例题3】龙珠游戏
题目
思路
首先读懂题目:输入的是一个排列
其次,这题和堆好像一点关系都没有
我的想法
理论复杂度\(O(n\sqrt n)\),其实从对拍来看,当\(n\le10^5\)时,速度和洛谷上\(O(n)\)标程相当.
几个数组:
int a[nn];//输入的数组
int p[nn];//p[i]表示数字i在a中的下标
int nxt[nn];//优化,nxt[i]=j表示区间(i,j)中所有vis值均为true
bool vis[nn];//若vis[i]==true,则a[i]已经加入到目标队列
不难想到,\(i\)从\(n\)往1找,若\(vis_{p_i}==false\),从\(p_i\)开始找第一个\(j\),使\(vis_j==false\)(若没找到,证明\(i\)是原龙珠序列的最后一个数),更新\(vis\),输出\(i\),\(a_j\)
时间复杂度可以去到\(O(n^2)\),需要优化
可以发现,不必要的时间用在找\(j\),因此,我们从这里入手
借助分块思想,如果找\(j\)的循环次数超过\(\sqrt n\),我们就更新\(nxt\)数组:
int las = n + 1;
for(int j = n ; j > 0 ; j--)
if(!vis[j])
nxt[j] = las , las = j;
这样,找\(j\)的循环次数基本不超过\(\sqrt n\),更新\(nxt\)数组的次数也不超过\(\sqrt n\),每次更新需要\(O(n)\)的时间
所以,时间复杂度为\(O(n\sqrt n)\),可通过
来自洛谷某题解
新鲜的题!!!
先用链表存储每一个编号的数前一个和后一个数的编号,最后由大到小枚举一遍,将没有用过的点连上后一个一起输出(注意它能输出,当且仅当它后面有数)
输出以后记得把它前一个数和它后面的后面的数连上
然后,就可以上代码了
#include <cstdio> int n,a[100001],k[100001],x[100001],i; int main() { scanf("%d",&n); for(i=1;i<=n;++i){ scanf("%d",&a[i]); k[a[i-1]]=a[i]; x[a[i]]=a[i-1];} for(i=n;i>=1;--i) if(k[i])printf("%d %d ",i,k[i]),k[x[i]]=k[k[i]],x[k[x[i]]]=x[i],k[k[i]]=0; return 0; }
代码
AC代码
#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return re;
}
int n;
int a[nn];
int p[nn];
int nxt[nn];
bool vis[nn];
int main() {
n = read();
for(int i = 1 ; i <= n ; i++)
p[a[i] = read()] = i;
for(int i = 1 ; i <= n + 1 ; i++)
nxt[i] = i + 1;
for(int i = n ; i > 0 ; i--) {
if(vis[p[i]]) continue;
int curnxt = nxt[p[i]];
int cnt = 0;
while(vis[curnxt] && curnxt <= n)
curnxt = nxt[curnxt] , ++cnt;
if(curnxt == n + 1)
continue;
printf("%d %d " , i , a[curnxt]);
vis[curnxt] = vis[p[i]] = true;
if(cnt * cnt >= n) {
int las = n + 1;
for(int j = n ; j > 0 ; j--)
if(!vis[j])
nxt[j] = las , las = j;
}
}
return 0;
}
随机数据
#include <bits/stdc++.h>
using namespace std;
int a[100010];
int main() {
unsigned seed;
cin >> seed;
seed *= time(0);
srand(seed);
int n = (long long)seed * rand() * rand() % 99999 + 1;
if(n & 1)++n;
cout << n << endl;
for(int i = 1 ; i <= n ; i++)
a[i] = i;
random_shuffle(a + 1 , a + n + 1);
for(int i = 1 ; i <= n ; i++)
printf("%d " , a[i]);
return 0;
}
D. 【例题4】工作安排
题目
思路
贪心+大根堆
我们把工作截止时间从大到小排序,设当前时间为\(d_i\)(注意此时第\(i\)项工作已经截止),把截止时间为\(d_i\)的工作全部放进堆里,设\(j<i\)且\(d_j\neq d_i\),\(j\)取最大值,我们若堆不为空,从堆中取出\(d_j-d_i\)个工作,把它们的\(P\)累加到答案中
输出答案即可
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 100010
#define ll long long
using namespace std;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return re;
}
struct Heap {
int siz;
int a[nn * 2] , b[nn * 2];
bool ty;
inline void swap_(int x , int y) {
int tmp;
tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;
tmp = b[x] ; b[x] = b[y] ; b[y] = tmp;
}
void clear() {
siz = 0 , ty = 0;
memset(a , 0 , sizeof(a));
}
inline void push(int dat , int dat2) {
a[++siz] = dat;
b[siz] = dat2;
int p = siz;
while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
swap_(p >> 1 , p) , p >>= 1;
}
inline void pop() {
swap_(1 , siz);
--siz;
int p = 1 , tmp;
while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
swap_(tmp , p) , p = tmp;
}
inline int top() {
return siz == 0 ? 0 : a[1];
}
inline bool empty() {
return siz == 0;
}
} h;
struct node {
int d , p;
} wk[nn];
bool cmp(node a , node b) {
return a.d > b.d;
};
int n;
signed main() {
// freopen("P2949_2.in" , "r" , stdin);
h.clear();
h.ty = 1;
n = read();
for(int i = 1 ; i <= n ; i++)
wk[i].d = read() , wk[i].p = read();
++n;
wk[n].d = wk[n].p = 0;
sort(wk + 1 , wk + n + 1 , cmp);
ll ans = 0;
h.push(wk[1].p , wk[1].d);
int i;
for(i = 2 ; i <= n && wk[i].d == wk[i - 1].d ; i++)
h.push(wk[i].p , wk[i].d);
while(i <= n) {
int num = wk[i - 1].d - wk[i].d;
while(num-- && !h.empty()) {
ans += (ll)h.top();
h.pop();
}
h.push(wk[i].p , wk[i].d);
for(++i ; i <= n && wk[i].d == wk[i - 1].d ; i++)
h.push(wk[i].p , wk[i].d);
}
cout << ans;
return 0;
}