大一寒假培训(七)——队列与优先队列

文章目录

    • 队列的定义
      • 队列的基本操作
          • 周末舞会
          • 取纸牌游戏
          • 海港(NOIP2016普及组真题)
          • Blash数集-队列-set
    • 优先队列
      • 优先队列的定义
      • 优先队列的定义方法
      • 用结构体定义优先队列(重载函数)
          • 合并果子-优先队列
          • 桐桐的新闻系统-优先队列
          • 序列合并-优先队列

疫情影响,导致我们宅在家里无(feng)所(kuang)事(bu)事(fan),但是一条消息终止了我们的行为
大一寒假培训(七)——队列与优先队列_第1张图片
于是我们又迎来了一轮培训。
首先花了两天时间学习了队列和优先队列,并且做了相应的习题。

队列的定义

队列就是允许在一端进行插入,在另一端进行删 除的线性表。允许插入的一端称为队尾,通常用一个 队尾指针r指向队尾元素,即r总是指向最后被插入的 元素;允许删除的一端称为队首,通常也用一个队首 指针f指向排头元素的前面。初始时f=r=0。

队列的基本操作

(1)初始化队列 queuevis,定义一个队列
(2)入队 vis.push(x)
(3)出队 vis.pop()
(4)判断队列是否为空 vis.empty()
(5)判断队列中元素的数量vis.size()
(6)得到队列的队首元素 vis.front()
综上: #include
则无需考虑头文件。

那么先来一道简单的例题。

周末舞会

假设在周末舞会上,男士们和女士们进入舞厅时,各自排 成一队。跳舞开始时,依次从男队和女队的队头上各出一人 配成舞伴。规定每个舞曲能有一对跳舞者。若两队初始人数 不相同,则较长的那一队中未配对者等待下一轮舞曲。现要 求写一个程序,模拟上述舞伴配对问题。
Input
第 1 行两个正整数,表示男士人数 m 和女士人数 n,1≤m,n≤1000;
第 2 行一个正整数,表示舞曲的数目 k,k≤1000。
Output
共 k 行,每行两个数,之间用一个空格隔开,表示配对舞伴的序号,男士在前,女士在后。
Sample Input
2 4
6
Sample Output
1 1
2 2
1 3
2 4
1 1
2 2

分析:设计两个队列分别存放男士和女士。每对跳舞 的人一旦跳完后就回到队尾等待下次被选。

queue<int>vis1,vis2;
int main()
{
     
	int n,m,k,s1,s2;
	cin>>n>>m;
	cin>>k;
	for(int i=1;i<=n;i++) vis1.push(i);
	for(int i=1;i<=m;i++) vis2.push(i);
	for(int i=1;i<=k;i++){
      
		s1=vis1.front(); vis1.pop(); vis1.push(s1); //取出对首,并将其放在队尾
		s2=vis2.front(); vis2.pop(); vis2.push(s2);	//同上
		cout<<s1<<" "<<s2<<endl;
	}

如果用数组来实现队列

const int maxn=1000;
int queue[maxn],counter=0,front=0,back=-1 ;
void push(int x)
{
     queue[++back]=x;++counter;}
int pop()
{
     --counter; return queue[front++];}

可能会越界,要关注maxn值的范围,推荐使用queue!

那么,再来一道例题

取纸牌游戏

小明正在使用一堆共 K 张纸牌与 N-1 个朋友玩取牌游戏。其中, N≤K≤100000,2≤N≤100,K 是 N 的倍数。纸牌中包含 M=K/N 张 “good”牌和 K-M 张“bad”牌。小明负责发牌,他当然想自己获得所有 “good”牌。
他的朋友怀疑他会欺骗,所以他们给出以下一些限制,以防小明耍诈:
1)游戏开始时,将最上面的牌发给小明右手边的人。
2)每发完一张牌,他必须将接下来的 P 张牌(1≤P≤10)一张一张地依 次移到最后,放在牌堆的底部。
3)以逆时针方向,连续给每位玩家发牌。
小明迫切想赢,请你帮助他算出所有“good”牌放置的位置,以便他得到所 有“good”牌。牌从上往下依次标注为 #1,#2,#3,…
Input
第 1 行,3 个用一个空格间隔的正整数 N、K 和 P。
Output
M 行,从顶部按升序依次输出“good”牌的位置。(就是从小到大输出)
Sample Input
3 9 2
Sample Output
3
7
8

解题思路:
发现“good”的牌都发给了小明自己,根据题目描述的N的倍数都发給了小明自己,所以当K 是N的倍数时,就存入到一个数组当中去。对于这个P要格外注意,当剩余的牌的个数小于P的时候也要 P次。
例如: p=3;
还剩下的编号为4和5;
第1次翻:5 4;
第2次翻:4 5;
第3次翻:5 4;
也是要翻3次的,哪怕就省下1张牌了,也要翻P次!

#include 
using namespace std;
int a[100005];
queue<int>vis;
int main()
{
     
	int n,k,p,num=0,z=0;
	cin>>n>>k>>p;
	for(int i=1;i<=k;i++)
		vis.push(i);
	while(!vis.empty()){
     
		int tmp=vis.front();
		vis.pop();
		num++;
		if(!(num%n))	//如果是n的倍数,则计数
			a[z++]=tmp;
		if(!vis.empty())
			for(int i=0;i<p;i++){
     	//移动p次牌
				tmp=vis.front();
				vis.pop();
				vis.push(tmp);
			}
	}
	sort(a,a+k/n);
	for(int i=0;i<k/n;i++)
		cout<<a[i]<<endl;
	return 0;
}
海港(NOIP2016普及组真题)

小K是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客。
小K对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况;对于第i艘到达的船,他记录了这艘船到达的时间ti (单位:秒),船上的乘客数k,以及每名乘客的国籍x1,x2,x3,x4等;
小K 统计了这N 艘船的信息,希望你帮助计算出每1艘船到达为止的24小时(86400秒)内到达的船上的乘客来自多少个国家?
Input
第1行为一个n,表示有n条船;
接下来有n行,每行前2个数为t和k,表示这艘船的到达时间和船上的旅客数量!
然后是这k个旅客的国籍(x1 x2 x3 …都是整数)
Output
输出n行,每行代表这艘船到达为止的24小时(86400秒)内到达的船上的乘客来自多少个国家?
t[i]-t[p]<=86400,t[i]表示当前船的时间,t[p]表示之前进海港的船!
1<=n,k<=300000; 1<=ti<=1000000000;
Sample Input
例子输入1:
3
1 4 4 1 2 2
2 2 2 3
10 1 3
例子输入2:
4
1 4 1 2 2 3
3 2 2 3
86401 2 3 4
86402 1 5
Sample Output
例子输出1:
3
4
4
例子输出2:
3
3
3
4

考虑用队列就可以,把每个乘客都分别以结构体的形式即进 入队列(是每一个乘客),结构体{时间,国籍},然后使用 桶排序的思想记录就可以了, 也可以用map来代替桶排序! 当一艘船进入海港时,先压入队列(每个人); 然后在从队 列头开始检查是否满足条件(出栈时计算总的国籍数是否变 化)
struct sa {
int t;//进港时间
int x;//国家
};
本题的难点就在于如何存储本题的数据,把每个人分别入队列就解决了船的问题,有点不好想。
难点:num[x]是桶排序的数组,ans是国家的数量。
1、根据num[x]==0 来判断是增加或减少一个国家。
2、每次新来的船,都先入队列,并统计国家数。
3、用刚新来船的时间和队列头部的船只时间进行比较
4、时间大于等于86400,就出队列,并计算国家数。
5、输出当前的国家数。

#include 
using namespace std;
struct peo{
     
	int t;
	int x;
};
queue<peo>vis;
int n,t,k,x,ans=0;
int num[100005];
int main()
{
     
	ios::sync_with_stdio(0);
	struct peo tmp;
	cin>>n;
	for(int i=0;i<n;i++){
     
		cin>>t>>k;
		for(int j=0;j<k;j++){
     
			cin>>x;
			vis.push({
     t,x});
			if(!num[x]) ans++;
			num[x]++;
		}
		while(t-vis.front().t>=86400){
     
			tmp=vis.front();
			vis.pop();
			int x1=tmp.x;
			num[x1]--;
			if(!num[x1]) ans--;
		}
		cout<<ans<<endl;
	}
	return 0;
}
Blash数集-队列-set

大数学家高斯小时候偶然间发现一种有趣的自然数集合 Blash ,对应以 a 为基的集合 Ba 定义如下:
(1)a 是集合 Ba 的基,且 a 是 Ba 的第一个元素。
(2)如果 x 在集合 Ba 中,则 2x+1 和 3x+1 也都在集合 Ba 中。
(3)没有其他元素在集合 Ba 中了。
现在小高斯想知道如果将集合 Ba 中元素按照升序排列,第 n 个元素会是多少?
Input
一行输入包括两个数字,集合的基 a 以及所求元素序号 n 。(1<=a<=50,1<=n<=100000)
Output
输出第n个元素的值!
Sample Input
1 100
Sample Output
418
Hint
样例2:
输入: 28 5437
输出:900585

#include 
using namespace std;
int a,n,m[1000005],t2,t3,t,jie;
int main()
{
     
	cin>>a>>n;
	m[1]=a;
	t2=t3=1;
	jie=2;
	while(jie<=n)		//求取第n小的数
	{
     
		int x2,x3;
		x2=m[t2]*2+1;		//2x+1在数集中
		x3=m[t3]*3+1;		//3x+1在数集中
		t=min(x2,x3);		//取x2,x3的较小值
		if(x2>x3)			//哪一方较小,就下标+1
			t3++;
		else
			t2++;
		if(m[jie-1]!=t)		//去重,如果t与前一个数相同,则不用放入数组
			m[jie++]=t;
	}
	cout<<m[n]<<endl;
	return 0;
}

这道题也可以使用set+queue做,set用来去重


优先队列

优先队列的定义

priority_queue翻译为优先队列,一般用来解决一些贪心问题,其底层是用“堆”来实现的。在优先队列中,任何时刻,队首元素一定是当前队列中优先级最高(优先值最大) 的那一个(大根堆),也可以是最小的那一个(小根堆)。 可以不断往优先队列中添加某个优先级的元素,也可以不断弹出优先级最高的那个元素,每次操作其会自动调整结构, 始终保证队首元素的优先级最高。

优先队列的定义方法

定义和使用priority_queue前,要添加queue头文件。
定义一个priority_queue的方法为: priority_queue< type > name;//type通常为结构体
其中,type可以是任何基本类型或者容器,name为优先队列的名字。
和queue不一样的是,priority_queue没有front()和back(),而只能通过top()或pop()访问队首元素(也称堆顶元素),也就是优先级最高的元素。
大根堆:从大到小排列。
小根堆:从小到大排列。
优先队列就是堆,也可以自己手写堆。
//小根堆的写法 priority_queue,greater< int > > vis;
//大根堆的写法 priority_queue,less< int > > vis;
当就是1个类型,可以这样直接写; 也可以用结构题

用结构体定义优先队列(重载函数)

typedef long long LL;
struct sa {
LL x;
LL y;
LL sum;
};
LL a[400005],b[400005];
bool operator < (const sa &a,const sa &b)
{
return a.sum >b.sum;//表示的是从小到大
}

合并果子-优先队列

Description
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3 种果子,数目依次为 1 , 2 , 9 。可以先将 1 、 2 堆合并,新堆数目为 3 ,耗费体力为 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 =3+12=15 。可以证明 15 为最小的体力耗费值。
Input
共两行。
第一行是一个整数 n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 ai(1≤ai ≤20000) 是第 i 种果子的数目。
Output
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2^31。
Sample Input
3
1 2 9
Sample Output
15

分析:
每次取2个数出来,合并后的数再压入优先队列!
再用1个变量累加记录每次取2个数的和!
队列:1 2 9
1+2=3; 把3压入队列;ans+=3;
队列:3 9
3+9=12;
ans+=12; 答案=15;如果队列为空就不放元素了!

#include 
using namespace std;
int n,x,ans;
priority_queue<int,vector<int>,greater<int> >q;
int main()
{
     
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=0;i<n;i++) {
     cin>>x;q.push(x);}
	while(q.size()>1){
     
		int t1=q.top();q.pop();
		int t2=q.top();q.pop();
		q.push(t1+t2);
		ans+=t1+t2;
	}
	cout<<ans<<endl;
	return 0;
}
桐桐的新闻系统-优先队列

Description
桐桐为期末的计算机作业设计了一套新闻系统,他把这套系统称为Argus;
使用这套系统的用户可以向这套系统注册,然后这套系统就会以用户要求发送新闻的时间间隔向用户发送一次新闻。向Argus注册的指令具有以下格式:
Register Q_num Period
Q_num(0 < Q_num<=3000)是用户的ID,Period(0 < Period <= 3000)是间隔。注册后Period秒,结果会第一次到达。所有的用户都有不同的Q_num。桐桐测试了一段时间后,想知道系统前K次给谁发送新闻了。如果同一时间发送多个新闻,以Q_num的升序排列。
Input
第一部分是注册指令,每条一行。指令数不超过1000,所有人指令同时执行完。此部分以“#”结束。
第二部分仅一行一个正整K,K<=10000
Output
输出前K个新闻发送到的用户的Q_num,每行一个。
Sample Input
Register 2004 200
Register 2005 300

5
Sample Output
2004
2005
2004
2004
2005

构造一个结构体
struct node{
int num,tim,sum;
} ;
tim是间隔时间,sum是总共的时间
这样先把所有的用户输入,再进行排序
一个用户被弹出队列后,将sum+=tim后,在插进队列里

#include 
using namespace std;
struct node{
     
	int num,tim,sum;
} ;
char a[10];
bool operator < (const node &s1,const node &s2)
{
     
	if(s1.sum!=s2.sum) return s1.sum>s2.sum;	//先按sum从小到大排序
	return s1.num>s2.num;						//再按num从小到大排序
}
int num,tim,k;
priority_queue<node,vector<node> >q;
int main()
{
     
	node tmp;
	ios::sync_with_stdio(0);
	while(cin>>a,a[0]!='#'){
     
		cin>>num>>tim;
		tmp.num=num;
		tmp.tim=tim;
		tmp.sum=tim;
		q.push(tmp);
	}
	cin>>k;
	while(k--){
     
		tmp=q.top();q.pop();
		cout<<tmp.num<<endl;	//输出num
		tmp.sum+=tmp.tim;
		q.push(tmp);			//重新插入队列
	}
	return 0;
}
序列合并-优先队列

Description
给出两个长度为 n 的有序表 A 和 B,在 A 和 B 中各任取一个元素,可以得到 n^ 2 个和,求这些和中最小的 n 个。
Input
第 1 行包含 1 个整数 n(1≤n≤400000)。
第 2 行与第 3 行分别有 n 个整数,各代表有序表 A 和 B。一行中的每两个整数之间用一个空格隔开,大小在长整型范围内,数据保证有序表单调递增。
建议用scanf()读入,否则会TLE!
Output
输出共 n 行,每行一个整数,第 i 行为第 i 小的和。
数据保证在 long long 范围内。
Sample Input
3
2 6 6
1 4 8
Sample Output
3
6
7

分析:
如果把n·n个数都算出来,一定会TLE
那么只能考察优化的方法,如何把前n个数放进去,也就是位置的先后入队列的顺序。
本题时间卡的很紧,用快读也没快多少,还是scanf()的好!
本题主要的思路,在纸上自己计算一下,先把第一行的和入优先队列, 然后从第2行的和开始,每出队列1个和就把这个位置下方的和压入队列!
例如坐标<1,1>的和出队列,就把坐标<2,1>的和压入队列

#include 
using namespace std;
typedef long long ll;
const int N=4e5+10;
int n,a[N],b[N];
struct node{
     
	int x,y;
	ll sum;
};
bool operator < (const node &s1,const node &s2)
{
     return s1.sum>s2.sum;}
priority_queue<node,vector<node> >q;
int main()
{
     
	ios::sync_with_stdio(0);
	node tmp;
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	for(int i=0;i<n;i++) cin>>b[i];
	tmp.y=0;
	for(int i=0;i<n;i++) tmp.x=i,tmp.sum=a[i]+b[0],q.push(tmp);
	for(int i=0;i<n;i++){
     
		tmp=q.top();q.pop();
		printf("%lld\n",tmp.sum);
		tmp.y++;tmp.sum=a[tmp.x]+b[tmp.y];
		q.push(tmp);
	}
	return 0;
}

你可能感兴趣的:(大一ACM寒假培训)