[Luogu T24367] 循环队列

本文介绍了一道洛谷上的算法题,题目要求玩家通过一系列操作找出特定条件下的序列元素。文章详细解释了解题思路,包括如何优化计算过程,避免重复工作,并通过哈希值比较序列。最终提供了一个高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门

题目背景

ShadyPi和LPA最近很无聊, 他们在纸上玩起了序列游戏。

题目描述

LPA写下了一个由nn 个数字组成的序列, ShadyPi不断地将序列尾端的一个元素放在了序列的前端(例:1 2 3 4 5 可以变成5 1 2 3 4 ), 这样就得到了n个序列。

现在他会对这些序列进行m - 1次操作, 每次将所有序列的所有元素加一。但很快LPA就觉得无聊了, 于是他每次在ShadyPi将所有元素加一后将所有元素对m取模。 现在他们想请你回答:在ShadyPi和LPA没有操作时, 操作1次时, 操作2次时……操作n - 1次时所有序列字典序最小的一个的第k个元素是什么。

输入输出格式
输入格式:

第一行三个正整数, 表示n, m,k。

第二行n个正整数, 表示原序列的所有元素。

输出格式:

共m行, 每行一个整数, 表示每次操作后的答案。

输入输出样例
输入样例:

5 6 3
1 2 1 2 3

输出样例:

1
2
3
5
5
0

说明

对于 30%的数据,1≤n,m≤100;

对于 100%的数据,1≤n,m≤50000, 1≤k≤n, 0≤a[i]

解题分析

首先, 考虑30分算法: 暴力枚举每个序列, 每次更新时暴力修改所有序列中所有元素, 再暴力找出最小的序列输出…复杂度 O(N2) O ( N 2 )

这样做会浪费大量时间,因为实际上每个序列有且仅有一次头元素变为0, 而若没有任何元素变为0则答案较前一轮一定只是 +1。所以我们可以在保存序列的时候先记录每个序列会在第几轮操作中变为0,在操作时只需要比较这些头元素变化为0的序列,输出字典序最小的一个。

现在我们需要处理的是如何快速比较两个序列。 考虑到所有序列的组成元素相同且排列顺序大致一样, 我们可以将原数组开为2倍长, 将同一元素分别存在 data[i] d a t a [ i ] data[i+n] d a t a [ i + n ] , 避免访问取模后面的序列的元素。接下来我们可以将每个字符串取得哈希值。

假设原序列为a, b, a, b ,c,x 哈希基数为N,以查询长度为2为例, 那么其前一个的哈希值为:(可以将a看做操作变成的0)

0 0

我们有另一个序列 a, b, c, x, a, b, 其前一个的哈希值为:

aN+b a N + b

如果我们要求它们前两个元素是否相同, 那么我们先将上述的每个哈希值乘以其查询长度与基数的乘积, 得到:

0 0

aN3+bN2 a N 3 + b N 2

而这两个序列向后移查询长度减一的哈希值分别为:

aN+b a N + b

aN3+bN2+aN+b a N 3 + b N 2 + a N + b

即得到了向后跳两个元素的哈希值减去中间两个元素的值的哈希值(感性理解), 这样就可以用原序列向后跳查询长度减一的哈希值减去其而得到中间的两个数的哈希值,从而判断是否拥有相同公共前缀。

于是我们拥有了判断两个序列是否有一定长度的方法, 我们只需要二分枚举最长前缀的长度再判断是否符合要求即可找到第一个前缀不同的地方, 即可判断哪一个序列字典序更小。因为所有序列有且只有一次机会使得头元素为0,所以判定总复杂度上界为 O(NlogN) O ( N l o g N ) , 加上每个“所有元素加一”操作的 O(N) O ( N ) ,最终复杂度为 O(NlogN) O ( N l o g N )

代码如下:

#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ul unsigned long long
#define ll long long 
#define MX 100005
#define Key 19491001
namespace Hash
{
    using std::swap;
    using std::vector;
    vector <int> zero[MX];
    ll base[2];
    ll key[2][MX], MOD[2];
    ll data[MX];
    ll hashval[2][MX];
    int ans[MX], tot, dot, kth;
    void prepare()
    {
        MOD[0] = Key;
        MOD[1] = Key;
        base[0] = 666233, base[1] = 1000000007;
        key[1][0] = key[0][0] = 1;
        for (R int i = 1; i < MX; ++i)
        {
            key[1][i] = key[1][i - 1] * base[1] % MOD[1];
            key[0][i] = key[0][i - 1] * base[0] % MOD[0];
        }
    }
    IN bool is_equal (int x, int y, const int &len)
    {//这一步一定要理解透彻
        if (!len) return true;
        if (x > y) swap(x, y);
        for (R int t = 0; t <= 1; ++t)
        {
            ll a = 0, b = 0;
            if(x > 0) a = hashval[t][x - 1] * key[t][len] % MOD[t];
            if(y > 0) b = hashval[t][y - 1] * key[t][len] % MOD[t];
            ll a1 = (hashval[t][x - 1 + len] - a + MOD[t]) % MOD[t];
            ll a2 = (hashval[t][y - 1 + len] - b + MOD[t]) % MOD[t];
            if(a1 != a2) return false; 
        }
        return true;
    }
    IN bool judge (const int &x, const int &y, const int &turn)
    {//二分查找LCP
        int lef = 0, rig = dot, mid, pos = -1;
        W (lef <= rig)
        {
            mid = (lef + rig) >> 1;
            if(is_equal(x, y, mid))
            {pos = mid, lef = mid + 1;}
            else rig = mid - 1;
        }
        if(pos == dot) return false;
        if((data[x + pos] + turn) % tot < (data[y + pos] + turn) % tot) return true;
        return false;
    }
}
using namespace Hash;
int main()
{
    prepare();
    scanf("%d%d%d", &dot, &tot, &kth);
    kth--;
    for (R int i = 0; i < dot; ++i)
    {
        scanf("%d", &data[i]);
        data[i + dot] = data[i];
        zero[(tot - data[i]) % tot].push_back(i);
    }
    ll A[2];
    A[0] = A[1] = 0;
    for (R int i = 0; i < dot * 2; ++i)
    {
        for (R int t = 0; t <= 1; ++t)
        {
            A[t] = A[t] * base[t] % MOD[t];
            A[t] = (A[t] + data[i]) % MOD[t];
            hashval[t][i] = A[t];
        }
    }
    int pos = 0;//处理原序列的情况
    for (R int i = 1; i < dot; ++i)
        if(judge(i, pos, 0)) pos = i;

    ans [0] = data[kth + pos];
    for (R int i = 1; i < tot; ++i)
    {
        if(zero[i].size() == 0) ans[i] = ans[i - 1] + 1;
      //如果没有序列头部变为0的情况则当前情况答案为上一个答案加1
        else
        {
            pos = zero[i][0];
            for (R int j = 1; j < zero[i].size(); ++j)
            {
                if(judge(zero[i][j], pos, i)) pos = zero[i][j];
            }
            ans[i] = (data[kth + pos] + i) % tot;
        } 
    }
    for (R int i = 0; i < tot; ++i) printf("%d\n", ans[i]);
        return 0;
}
题目链接:https://www.luogu.com.cn/problem/P3522 //一个单调队列记录最低温度最大值,另一个记录最高温度最小值 //当窗口内最低温度的最大值大于等于当前窗口之前最高温度的最小值时 //说明当前窗口内存在一段非降序列.如果不存在这种情况,就将窗口左边界右移 //直到满足条件为止 ///算法:单调队列 //时间复杂度:O(n) #include <iostream> #include <vector> #include <deque> #include <algorithm> #define int long long using namespace std; const int N = 1e6+9; int n; pair<int, int> t[N]; inline void Faith(){ cin >> n; for(int i = 0; i < n; ++i) cin >> t[i].first >> t[i].second; deque<pair<int, int>> l,r; int ans = 0, id = 0; for(int i = 0; i < n; ++i){ while(!l.empty() && l.back().second <= t[i].first) l.pop_back();//维护最大l的单调递减队列 l.push_back({i, t[i].first}); while(!r.empty() && r.back().second >= t[i].second) r.pop_back();//维护最小r的单调递增队列 r.push_back({i, t[i].second}); while(!l.empty() && !r.empty() && l.front().second > r.front().second){//当前窗口不合适,移动左指针 id = max(id, min(l.front().first+1, r.front().first+1)); if(!l.empty() && l.front().first < id) l.pop_front(); if(!r.empty() && r.front().first < id) r.pop_front(); } if(!l.empty() && !r.empty()){ ans = max(ans, i-id+1); } } cout << ans << "\n"; } signed main(void){ ios::sync_with_stdio(false); cin.tie(nullptr),cout.tie(nullptr); Faith(); return 0; } 48分,哪里有问题?>
最新发布
06-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值