算法与数据结构实验题 4.19 优质的随从慕名而来
实验任务
YH学长想在酒馆战旗中赢得第一名,因此他要找Bob用漏斗蛋糕招揽一些优质的随从!
有n个随从慕名而来,第i个随从的种类是ai。YH学长十分贪心,他每个随从都想要,因此他准备了充足的黄金铸币并依次买下每一个随从。但即使如此,他也只能携带m个随从,当新买的随从无法加入队伍时,YH学长只好卖掉队伍里之前最早买下的随从。
“啊哈,三连!”,机智的YH学长发现利用酒馆中的三连机制能够让随从们变得更加强大。当YH学长买下一个种类为x的随从时,若队伍里有另外两个随从种类也为x,他就凑成了一个“三连”。此时“三连”会将队伍中两个种类为x的随从移除,然后使买下的那个随从变为“金色”并加入队伍。
但为了防止YH学长的队伍变得太强,每当YH学长获得“金色”随从时,JC学长就会偷偷卖掉这个”金色“随从。JC学长想知道最后他需要卖掉多少YH学长的”金色“随从。
数据输入
第一行包含两个正整数 n,m(1<=m<=n<=1e6),表示随从的个数和队伍的容量。
第二行包含n个整数 ai(1<=ai<=1e6),表示第i次买下的随从的种类。
数据输出
输出一个整数,表示JC学长卖出的“金色”随从的个数
输入示例
10 3
2 1 1 1 2 3 3 1 3 1
输出示例
2
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=2e9+1;
int n,m,x,num[1000010],que[1000010],ans,cnt;
queue<int>q;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) {
scanf("%d",&x);
num[x]++;
q.push(x);
if(num[x]==3) {
ans++;
num[x]-=3;
que[x]+=3;
cnt+=3;
}
if(q.size()-cnt>m) {
while(que[q.front()]>0){
que[q.front()]--;
q.pop();
cnt--;
}
num[q.front()]--;
q.pop();
}
}
printf("%d",ans);
return 0;
}
思路
输入数据的数量n,队列大小为m+1,之后为n个数据,我们用a1,a2……ai来表示,将这些数字依次放入队列,若队列中出现三个相同的数字时,ans++,将三个相同数字弹出队列
若直接考虑模拟的话,在已有队列中找到一个数的时间复杂度为O(n)效率堪忧,若每次都去找到后删除,必然会超时,因此考虑其它算法
第一个要解决的问题是:记录每个数字的出现次数,那么用一个数组记录即可,若数据后期增大到1e9,则可以通过unordered_map的哈希表形式进行储存,每一次循环读入代码如下
scanf("%d",&x);
num[x]++; //x的数量加一
q.push(x); //采用stl中的queue
第二个要解决的问题是,怎么样高效地实现“删除三个相同数据”
首先,我们需要删除操作时,当且仅当队列已满;既然我们在删除时都需要将n个数据出队一遍,那么不妨考虑在出队时,对“三连”的数字进行处理,即在入队时进行标记操作,在最后一并处理,可以将程序的时间复杂度降至整体O(n)
if(num[x]==3) {
ans++; //三连时答案加一
num[x]-=3; //将三个人清出队列(数量减三)
que[x]+=3; //懒标记,在出队操作时需要一并 移除的数量
cnt+=3; //队列大小暂时扩充3,出队时收缩
}
因此,出队时的代码也就很明确了
if(q.size()>=m+1+cnt) { //当队列大小大于原有大小加扩充值
while(que[q.front()]>0) { //当碰到打上标记的数据时
que[q.front()]--; //标记数量减一
q.pop(); //出队标记元素
cnt--; //队列扩充大小减一
}
num[q.front()]--; //将本来需要出队的数据出队
q.pop(); //数据数量减一
}
本题考验了我们对队列这一数据结构的时间复杂度熟练度,以及“用空间换时间”的实践思考,通过“打标记”这一操作,可以成功地把时间复杂度降至O(n)