队列

小组队列

有n个小组要排成一个队列,每个小组中有若干人。
当一个人来到队列时,如果队列中已经有了自己小组的成员,他就直接插队排在自己小组成员的后面,否则就站在队伍的最后面。
请你编写一个程序,模拟这种小组队列。
1、ENQUEUE x - 将编号是x的人插入队列;
2、DEQUEUE - 让整个队列的第一个人出队;
3、STOP - 测试用例结束

可以采用一个小组队列和一个队列数组完成要求,小组队列记录小组编号的排序,队列数组记录每个小组的内部排序。

#include <iostream>
#include <queue>
#include <string>

using namespace std;

//h是哈希表,记录每个人所属的组别
int h[1000010];

int main(){
	//n表示有多少小组,当n为0结束,t是每个小组的人数,id是小组编号
    int n, t, num, id, index = 1;
    string s;
    while(cin >> n, n){
        cout << "Scenario #" << index++ << endl;
        //master队列记录小组的顺序,team队列数组记录每个小组内部的排序
        queue<int> master;
        queue<int> team[n];
        for(int i=0; i<n; i++){
            cin >> t;
            for(int j=0; j<t; j++){
                cin >> num;
                h[num] = i;
            }
        }
        while(cin >> s, s != "STOP"){
        	//如果是插入,查找哈希表找到对应组别是否存在,如果存在直接插入,否则要先将小组编号插入到master
            if(s == "ENQUEUE"){
                cin >> num;
                id = h[num];
                if(team[id].empty()){
                    master.push(id);
                }
                team[id].push(num);
            }
            //如果是出列,先找到master队首,得到小组编号进而找到对应小组的队首弹出,如果弹出后为空要记得弹出master队首
            else{
                id = master.front();
                cout << team[id].front() << endl;
                team[id].pop();
                if(team[id].empty()) master.pop();
            }
        }
        cout << endl;
    }
    
    return 0;
}
蚯蚓

蛐蛐国最近蚯蚓成灾了!
隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。
蛐蛐国里现在共有 n 只蚯蚓,第 i 只蚯蚓的长度为 ai ,所有蚯蚓的长度都是非负整数,即可能存在长度为0的蚯蚓。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只,将其切成两段。
若有多只最长的,则任选一只。
神刀手切开蚯蚓的位置由有理数 p 决定, 介于0和1之间。
一只长度为 x 的蚯蚓会被切成两只长度分别为 ⌊px⌋ 和 x−⌊px⌋ 的蚯蚓。
特殊地,如果这两个数的其中一个等于0,则这个长度为0的蚯蚓也会被保留。
此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加一个非负整数 q 。
蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。
蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要 m 秒才能到来。
蛐蛐国王希望知道这 m 秒内的战况。
具体来说,他希望知道:
m 秒内,每一秒被切断的蚯蚓被切断前的长度,共有 m 个数。
m 秒后,所有蚯蚓的长度,共有 n+m 个数。

如果仅仅是对现有的蚯蚓切成两段,我们只需排序即可,但每次切断都会产生新的蚯蚓,如果每次都要重新排序,时间复杂度就是 O ( m n ) O(mn) O(mn)。我们能否从每次切段中找到规律来降低时间复杂度呢?对于某个i时刻切断的蚯蚓 x i x_i xi,分成两部分 x i − ⌊ p x i ⌋ x_i-⌊px_i⌋ xipxi ⌊ p x i ⌋ ⌊px_i⌋ pxi。而对于之后的某个j时刻切断的蚯蚓 x j x_j xj,分成两部分 x j − ⌊ p x j ⌋ x_j-⌊px_j⌋ xjpxj ⌊ p x j ⌋ ⌊px_j⌋ pxj。如果 x i > = x j x_i>=x_j xi>=xj,那么一定有 x i − ⌊ p x i ⌋ > = x j − ⌊ p x j ⌋ x_i-⌊px_i⌋>=x_j-⌊px_j⌋ xipxi>=xjpxj ⌊ p x i ⌋ > = ⌊ p x j ⌋ ⌊px_i⌋>=⌊px_j⌋ pxi>=pxj

x i − ⌊ p x i ⌋ = ⌈ x i − p x i ⌉ = ⌈ ( 1 − p ) x i ⌉ x_i-⌊px_i⌋= ⌈x_i-px_i⌉=⌈(1-p)x_i⌉ xipxi=xipxi=(1p)xi,对 x j x_j xj同理 ⌈ ( 1 − p ) x j ⌉ ⌈(1-p)x_j⌉ (1p)xj,因为 x i > = x j x_i>=x_j xi>=xj,所以 x i − ⌊ p x i ⌋ > = x j − ⌊ p x j ⌋ x_i-⌊px_i⌋>=x_j-⌊px_j⌋ xipxi>=xjpxj

因此我们可以知道先切断蚯蚓的两段一定分别大于等于后切断蚯蚓的两段。因此我们只需要维护三个队列,一个是原队列,另两个分别是切断以后的两段。在每次取最长蚯蚓的时候,只需要比较三个队列的队首就可以。

而对于增长长度,我们只需要额外维护一个变量就可以,每次取出长度以后加上这个变量,存储时减去这个变量。

#include <iostream>
#include <algorithm>
#include <limits.h>

using namespace std;

//n是蚯蚓数量,m是时间,q是单位时间增长长度,u/v是p,t是输出长度的时间间隔。
int n,m,q,u,v,t;
//o是原始长度,l是左半部分,r是右半部分
int o[100010], l[10000010], r[10000010];
//如下是指针,f代表前指针,b代表后指针
int of, ob, lf, lb, rf, rb;

//返回三个队首中最大的那个长度
int get_max(){
    int ans = INT_MIN;
    if(of < ob) ans = max(ans, o[of]);
    if(lf < lb) ans = max(ans, l[lf]);
    if(rf < rb) ans = max(ans, r[rf]);
    if(of < ob && ans == o[of]) of++;
    else if(lf < lb && ans == l[lf]) lf++;
    else rf++;
    return ans;
}

int main(){
	//left和right分别是左右长度,delta是长度偏移量,记录增长长度
    int num, left, right, delta = 0;
    cin >> n >> m >> q >> u >> v >> t;
    //降序排序
    for(int i=0; i<n; i++){
        cin >> o[i];
    }
    sort(o, o+n);
    reverse(o, o+n);

    ob = n;
    for(int i=1; i<=m; i++){
    	//每次取出最大的长度加上偏移量
        num = get_max() + delta;
        if(i%t == 0) cout << num << " ";
        //分成两部分
        left = num * 1ll * u / v;
        right = num - left;
        //加上增长长度
        delta += q;
        //push进队尾
        l[lb++] = left -delta;
        r[rb++] = right -delta;
    }
    cout << endl;
    
    //返回m秒后所有蚯蚓的长度
    for(int i=1; i<=m + n; i++){
        if(i%t == 0) cout << get_max() + delta << " ";
        else get_max();
    }

    return 0;
}
双端队列

达达现在碰到了一个棘手的问题,有N个整数需要排序。
达达手头能用的工具就是若干个双端队列。
她从1到N需要依次处理这N个数,对于每个数,达达能做以下两件事:
1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;
2.将当前数放入已有的队列的头之前或者尾之后。
对所有的数处理完成之后,达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列。
请你求出最少需要多少个双端序列。

分析题意,我们可知如果以数值大小为横轴,处理时间为纵轴,那么每个双端队列的大致形状分成如下三种:
在这里插入图片描述
所以我们可以对所有输入数据进行排序,找寻下凹图形的数量,每个下凹图形分割两个双端队列,由此可以找到最小双端队列的数量。例如对于一个排序序列,我们应该按照虚线进行分割
在这里插入图片描述
但以上都是假设不存在重复数值的情况,对于有重复数字,我们应该分情况考虑,我们的目的就是使整个数据出现尽可能少的转折。
当之前的数列是下降的,我们应该将重复数值最大的输入时间与之前数列的末尾比较,如果小于,那么重复数值应该按照输入时间降序排序,如果大于应当升序排序。如下图,通过反证可知,这样才能使数据尽可能少转折。
在这里插入图片描述

当之前的数列是上升的,我们应该将重复数值最小的输入时间与之前数列的末尾比较,如果大于,那么升序排列;小于就降序排列,此时会出现下凹转折,分段+1。类比上图。

#include <iostream>
#include <algorithm>

using namespace std;

//n是输入数据个数,res是结果,dir记录此前数据的趋势
int n, res, dir;
//nums是输入数据,first代表值,second代表输入时间(次序)
pair<int, int> nums[200010];

int main(){
    cin >> n;
    //边界条件
    nums[0].second = 1e9;
    for(int i=1; i<=n; i++){
        cin >> nums[i].first;
        nums[i].second = i;
    }
    //按照值大小排序
    sort(nums+1, nums+n+1);
    
    //分情况讨论
    int start;
    for(int i=1; i<=n; i++){
        start = i;
        while(i<n && nums[i].first == nums[i+1].first) i++;
        if(dir == 0){
            if(nums[i].second > nums[start-1].second) dir = 1;
            else dir = 0;
        }
        else{
            if(nums[start].second > nums[start-1].second) dir = 1;
            else res++, dir = 0;
        }
        if(start < i)
            if(dir == 0) sort(nums+start, nums+i+1, greater<pair<int, int>>());
    }
    
    cout << res + 1;
    
    return 0;
}
最大子序和

输入一个长度为n的整数序列,从中找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是1。

和最大,能想到利用前缀和,但如果利用前缀和暴力求解也要 O ( n 2 ) O(n^2) O(n2)的时间。我们可以利用单调队列,维护一个单调递增的序列,对于每个元素找到队首元素相减,就是满足长度要求的序列。关于维护单调队列,如果队首元素和当前元素距离超过m,那么弹出;将当前元素压入单调队列,并弹出大于该元素的队尾。

#include <iostream>
#include <deque>
#include <limits.h>

using namespace std;

//n是数据数量,m是窗口大小
int n, m;
//res是最大和,sum是前缀和,dq是单调序列
long long res = INT_MIN;
long long sum[300010];
deque<int> dq(1, 0);

int main(){
    cin >> n >> m;
    for(int i=1; i<=n; i++){
        cin >> sum[i];
        sum[i] += sum[i-1];
    }

    for(int i=1; i<=n; i++){
        while(dq.size() && dq.front() < i - m) dq.pop_front(); //超出窗口范围,清除
        res = max(res, sum[i]-sum[dq.front()]); //更新值
        while(dq.size() && sum[dq.back()] > sum[i]) dq.pop_back(); //维护单调性质
        dq.push_back(i);
    }
    cout << res;

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值