洛谷P1198 [JSOI2008]最大数

P1198 [JSOI2008]最大数

题目描述

现在请求你维护一个数列,要求提供以下两种操作:

1、 查询操作。

语法:Q L

功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。

限制:L不超过当前数列的长度。

2、 插入操作。

语法:A n

功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。

限制:n是整数(可能为负数)并且在长整范围内。

注意:初始时数列是空的,没有一个数。

输入输出格式

输入格式:

 

第一行两个整数,M和D,其中M表示操作的个数(M <= 200,000),D如上文中所述,满足(0<D<2,000,000,000)

接下来的M行,每行一个字符串,描述一个具体的操作。语法如上文所述。

 

输出格式:

 

对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。

 

输入输出样例

输入样例#1:
5 100
A 96
Q 1
A 97
Q 1
Q 2
输出样例#1:
96
93
96

说明

[JSOI2008]

分析:这道题有很多种办法解决,首先可以发现数列中的数是递增的,每次添加进去的数都比之前的大,那么根据这个原理,模拟一下就能做出来.

这道题可以用来练线段树,为什么想到要用线段树呢?注意区间二字!在区间中查找最大值并且完成单点修改(插入),这不就是线段树的最基本的操作吗?因为线段树可以全部赋值为-inf,所以插入操作可以理解为单点修改,套用线段树的模板即可解决.

变量开成了全局变量,查了一个晚上的错......

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>#define le l,mid,o * 2
#define re mid + 1,r,o * 2 + 1using namespace std;const int maxn = 200001;int m, d,maxo[maxn << 2],len,t;void build(int l,int r,int o)
{if (l == r){maxo[o] = -2147283647;return;}int mid = (l + r) >> 1;build(le);build(re);
}void charu(int l, int r, int o, int i, int j)
{if (l == r) { maxo[o] = j;return; }int mid = (l + r) >> 1;if (i <= mid)charu(le, i, j);else charu(re, i, j);maxo[o] = max(maxo[o * 2], maxo[o * 2 + 1]);
}int query(int l, int r, int o, int x, int y)
{if (x <= l && r <= y)return maxo[o];int mid = (l + r) >> 1;int temp = -2147483647;if (x <= mid)temp = max(temp,query(le, x, y));if (y > mid)temp = max(temp, query(re, x, y));return temp;
}int main()
{scanf("%d%d", &m, &d);build(1, maxn, 1);for (int b = 1;b <= m;++b){char c;int i;cin >> c;scanf("%d", &i);if (c == 'A') { len++;charu(1, maxn, 1, len, (i + t) % d); }else { t = query(1, maxn, 1, len - i + 1, len);printf("%d\n", t); }}return 0;
}

 

如果你还不会线段树,那么可以参考一下下面这段文字(之前写的可能不是很好,望体谅,可能也有一些不正确的,只能当作参考):

线段树,这个万能的树。

线段,线段,说白了就是一个区间,线段树主要的操作就是对区间进行修改查询,效率非常高,线段树的用途非常广,单点更新、区间更新、最值询问、区间询问,至于它具体能干哪些事取决于树里所储存的信息量。

 

这是一个线段树的图,这个图只是能够帮我们理解线段树的大体形状,并不能告诉我们更多信息,其实线段树的更多功能都隐藏在每一个节点的信息背后,为了能够更方便的做题,我们给线段树的每一个节点标上序号。我们从上到下,从左到右依次标号,如果根节点的序号为k,那么它的左子树节点的序号则为2k,右子树节点的序号则为2k + 1,每一个序号都对应着唯一一个节点,所以我们可以用一个数组tree来表示这个节点背后所隐藏的信息。这个数组究竟开多大呢?虽然在平常做题中我们不需要考虑的这么仔细,但在一些内存限制非常紧的题目中这些都是要注意的。如果区间范围是[0,N-1],那么tree的大小M=2*N + 1,这个很好验证。

我们先来考虑如何建树,一般来说,只要到了叶子节点直接输入就好了,但是我们怎么样才能够很快的到达叶子节点呢?递归!

int tree[2 * MAX_N + 1];

 

/*建立以k为根节点[L,H]为操作区间的线段树*/

void built_tree(int k, int L, int H)

{

    if (L == H){

        scanf("%d", &tree[k]);

        return;

    }

    built_tree(k << 1, L, (L + H) << 1);

    built_tree(k << 1 | 1, (L + H) << 1 | 1, H);

}

如果L==H,证明当前区间的长度为1,也就是此节点为叶节点,可以直接赋值。

再来考虑一个经典问题:求一个区间内的最小元素值。

这道题可以用暴力来做,不过复杂度太高,在一些题目中可能会TLE,我们可以看到区间二字,那么这道题80%要用线段树来做(当然也不是绝对,只是效率高),我们不断比较当前查询区间和目标区间,如果当前查询区间在目标区间内,那么当前深度所表示的节点便可以参与最小值计算,如果不在区间内,则返回无穷大,否则则分别对当前树的左右子树进行相同运算(可能术语话太强了).

int read_tree(int k, int L, int H, int beg, int end)

{

    if (beg > H || end < L) return -INT_MAX;

    if (beg <= L && end >= H) return tree[k];

    return min(read_tree(2 * k, L, (L + H) / 2, beg, end),

        read_tree(2 * k + 1, (L + H) / 2 + 1, H, beg, end));

}

有查询,就一定伴随着修改的存在,如果是普通的数组,修改很容易,只需要对所需要操作的下标所对应的数据修改即可,但是这是高效率数据结构,修改就意味着要对许多量进行改变,在线段树中,我们对一个节点进行修改只需要对其及其所有的祖先进行修改即可,其他量不变。

/*在根节点为k,[L,H]为操作区间的线段树里对id处的值更新为key*/

int update_tree(int k, int L, int H, int id, int key)

{

    if (L == H){

        tree[k] = key;

        return;

    }

    if (id < (L + H) / 2)

        update(k * 2, L, (L + H) / 2, id, key);

    else

        update(K * 2 + 1, (L + H) / 2 + 1, H, id, key);

    tree[k] = MAX(tree[k * 2], tree[2 * k + 1]);

}

这样便完成了修改操作.

然后是比较复杂的区间修改,设计一个数据结构,使它支持两种操作

  1. Add(L,R,v)将AL,AL+1…AR的值全部+V
  2. Query(L,R)计算子序列AL,AL+1…AR的元素和,最小值,最大值。

这里要维护三个查询值,该怎么维护呢?

首先这里的Add操作是区间修改,并不是单点修改,最糟糕的情况下可能整棵线段树的结点值都要被修改。我们知道线段树任意区间都能分解成不超过2h个不相交区间的并,利用这个结论我们可以将每一个Add操作分解成不超过2h个的Add操作,记录在线段树的结点中。每次执行完Add操作都要重新计算每个结点的附加信息,递归访问到的结点全部都要重新计算,并且是在递归返回后计算!

下面给出计算的代码:

void weihu(int o,int L,int R)

{

    int lc = o * 2, rc = o * 2 + 1;

    sumv[o] = minv[o] = maxv[o] = 0;

    if (R > L) {

        sumv[o] = sumv[lc] + sumv[rc];

        minv[o] = min(minv[lc], minv[rc]);

        maxv[o] = max(maxv[lc], maxv[rc]);

    }

    minv[o] += addv[o];

    maxv[o] += addv[o];

    sumv[o] += addv[o] * (R - L + 1);

}

对于下面的代码来说,修改/查询的范围均为[y1,y2].

这里的sumv数组要说一下,为什么要用左右子结点相加得出呢?首先父亲结点就包含了左右结点,其次这样维护的时候就不需要修改全部的sumv数组的元素了。当然这里指的是特殊情况,一般是那种极端数据的。

下面是Add操作的代码:

void Add(int o, int L, int R)

{

    int lc = o * 2, rc = o * 2 + 1;

    if (y1 <= L && y2 >= R)

        addv[o] += v;

    else {

        int M = (L + R) >> 1;

        if (y1 <= M)

            Add(lc, L, M);

        if (y2 > M)

            Add(rc, M + 1, R);

    }

    weihu(o, L, R);

}

其中addv数组是累加边界的add值,因为一棵线段树的子节点可能不知被修改一次,所以有必要设立这个数组。

然后是查询操作,话说用线段树步步都得谨慎,感觉这句话没错啊,每次进行操作都要考虑到结点对结点之间有没有影响,我们查询一般都是从上往下递归查询,既然一个结点的父结点执行了add操作,而这个节点也被父节点包括在内,所以这个节点的值肯定被改变了,于是我们只能设3个全局变量来维护。

int _min, _max, _sum;

void query(int o, int L, int R, int add)

{

    if (y1 <= L && y2 >= R) {

        _sum += sumv[o] + add * (R - L + 1); \

            _min = min(_min, minv[o] + add);

        _max = max(_max, maxv[o] + add);

    }

    else {

        int M = (L + R) >> 1;

        if (y1 <= M)

            query(o * 2, L, M, add + addv[o]);

        if (y2 > M)

            query(o * 2 + 1, M + 1, R, add + addv[o]);

    }

}

看到很多人都弄混了,好吧,其实我也有点晕了。可能会有人问了,为什么我们的weihu函数已经维护了现在还要维护呢?因为weihu函数是从下到上的,也就是从左右子节点维护的,是相对于子节点所发生的变化,而这里的全局变量是因为父节点进行了Add操作,子节点包含在内,所以要另开变量维护。如果还是搞不明白,可以看到weihu函数最后只是修改了当前节点的值,并没有维护到它的子节点,所以要另开变量维护。

接下来是更加复杂的:

Set(L,R,v)把AL,AL+1...AR的值全部修改为v.

Query(L,R)计算子序列AL,AL+1...AR的三个值(同上题).

可以看到这里变动的是Set操作,我们说这道题比之前复杂,为什么呢?因为之前的Add操作不管操作次序如何,都可以达到最后的结果,前提是算法是对的,代码没写错。然而Set操作则不同,好比刷油漆,最后刷的就是最终颜色。怎么办呢?打标记!这里的打标记则相当于对于被改变的特殊情况而做的变动,是为了最后的求出三个值而打的,那么怎么做呢?如果当前区间完全被包含在我们需要修改/查询的区间内,则直接修改标记为v,否则则标记下传。

void pushdown(int o)

{

    int lc = o * 2, rc = o * 2 + 1;

    if (setv[o] >= 0)

    {

        setv[lc] = setv[rc] = setv[o];

        setv[o] = -1;

    }

}

这里的setv数组即为标记,注意到这个数组被初始化为-1,这里不要搞错了,那么问题来了:为什么我们要清除父节点的标记呢?

接下来,Set操作代码:

void Set(int o, int L, int R)

{

    int lc = o * 2, rc = o * 2 + 1;

    if (y1 <= L && y2 >= R)

    {

        setv[o] = v;

    }

    else {

        pushdown(o);

        int M = (L + R) >> 1;

        if (y1 <= M)

            Set(lc, L, M);

        else

            maintain(lc, L, M);

        if (y2 > M)

            Set(rc, M + 1, R);

        else

            maintain(rc, M + 1, R);

    }

    maintain(o, L, R);

}

注意到3次maintain,最后一次很好理解,因为我们之前讲过,每一次递归完后都必须要维护一次,那么前两次又是为何呢?因为标记一旦下传,则该子树的附加信息需要改变,当前区间内的子树在递归完后自然会进行维护,不过另一个区间内的子树则没有被维护,因此需要加上两次maintain函数的调用。

接下来是query操作的代码:

void query(int o, int L, int R)

{

    if (setv[o] >= 0) {

        _sum += setv[o] * (min(R, y2) - max(L, y1) + 1);

        _min = min(_min, setv[o]);

        _max = max(_max, setv[o]);

    }

    else if (y1 <= L && y2 >= R)

    {

        _sum += sumv[o];

        _min = min(_min, minv[o]);

        _max = max(_max, maxv[o]);

    }

    else {

        int M = (L + R) >> 1;

        if (y1 <= M)

            query(o * 2, L, M);

        if (y2 > M)

            query(o * 2 + 1, M + 1, R);

    }

}

对于有标记的区间,我们要优先处理,首先知道当前区间都被修改为setv[o],既然所有值都是一样的,自然就是对其进行操作,然后再考虑被所要查询的区间所完全包围。回到之前的问题上来,为什么我们要清除父节点的标记呢?我们将标记下传一般都是传到被所要查询的区间所完全包围的区间,因为子节点的区间内的值就包含了大区间的值,换句话说,所求的结果就是几个小区间的并,而这几个小区间则是分解到不能再分解为止,自然,我们将父节点的标记消除因为子节点才是影响到结果的根本,我们求的值最终也在子节点进行,所以要消除.

 

                                                        

转载于:https://www.cnblogs.com/zbtrs/p/5851173.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/270928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

njx如何实现负载均衡_负载均衡是怎么做的~

展开全部1、服务直接返回&#xff1a;这种安装方式负载均衡的LAN口不使用&#xff0c;WAN口与服务器在同一个网络中&#xff0c;互联网的32313133353236313431303231363533e78988e69d8331333431363531客户端访问负载均衡的虚IP(VIP)&#xff0c;虚IP对应负载均衡机的WAN口&…

电脑技巧:C盘爆满该如何清理,实用的清理方案,小白必备

有用户和小编说&#xff0c;C盘就像是一个无底洞&#xff0c;无论给它分多大的分区&#xff0c;Windows操作系统总有办法给它填满&#xff01;相信很多朋友也有这样的感受吧&#xff1f;其实&#xff0c;好像休眠文件、系统页面文件等等GB大“人物”是驻扎在C盘的&#xff0c;此…

linux中profile文件作用,解析Linux系统中bashrc和profile文件的作用区别

使用终端ssh登录Linux操作系统的控制台后&#xff0c;会出现一个提示符号(例如&#xff1a;#或~)&#xff0c;在这个提示符号之后可以输入命令&#xff0c;Linux根据输入的命令会做回应&#xff0c;这一连串的动作是由一个所谓的Shell来做处理。Shell是一个程序&#xff0c;最常…

操作系统:电脑的回收站的秘密你知道吗?

电脑的回收站的秘密你知道吗&#xff1f; 今天小编给大家介绍一下有关电脑回收站的相关知识&#xff0c;赶紧来看看吧&#xff01; 回收站是所有磁盘驱动空间中的一个区域。 鼠标右键打开电脑桌面回收站的属性面板,在属性面板中可以看到所有的系统驱动程序使用了同一设置选项,可…

【Qt开发】QSplitter的使用和设置

Qt库版本&#xff1a;5.2.1 Qt Creator版本&#xff1a;3.0.1 1 QSplitter的用途 QSplitter使得用户可以通过拖动子窗口之间的边界来控制它们的大小&#xff0c;例如 图1 窗口拆分示意图 2 QSplitter的添加方法 QSplitter的添加方法有2种&#xff1a;a)通过Qt Creator的界面设计…

异星工厂mod位置linux,异星工厂存档在哪里

异星工厂存档在哪里想必有些小伙伴还不是很清楚的吧&#xff0c;所以呢今天小编就为大家带来了异星工厂MOD安装位置介绍&#xff0c;一起来了解一下吧。异星工厂存档在哪里%appdata%/factorio等同于C:\Users\您的用户名\AppData\Roaming\Factorio因为各位的电脑用户名不一样。所…

pytorch 画loss曲线_Pytorch使用tensorboardX可视化。超详细!!!

1 引言我们都知道tensorflow框架可以使用tensorboard这一高级的可视化的工具&#xff0c;为了使用tensorboard这一套完美的可视化工具&#xff0c;未免可以将其应用到Pytorch中&#xff0c;用于Pytorch的可视化。本文主要是针对该解决方案提供一些介绍。TensorboardX支持scalar…

电脑技巧:电脑键盘F1~F12按键的妙用

目录 F1&#xff1a;帮助键 F3&#xff1a;搜索按键 F4:打开浏览器历史列表 F5&#xff1a;刷新功能 F6&#xff1a;定位地址栏 F7&#xff1a;在“命令提示符”中调用历史指令 F8&#xff1a;启动系统高级菜单 F9&#xff1a;无 F10&#xff1a;需要与Shift组合使用&#xff0…

linux vim基本操作,vim基本操作笔记

在Linux系统中有多种代码编辑器&#xff0c;例如vim, gedit, emacs。这这些编辑器各有所长&#xff0c;就我个人而言&#xff0c;对于比较短的代码&#xff0c;一般可以用vim解决就不用其它的工具&#xff0c;而长代码的情况下更喜欢用gedit&#xff0c;这个gnome自带的代码编辑…

iOS 获取当前对象所在的VC

id next [self nextResponder] ;while (next ! nil) {next [next nextResponder];if ([next isKindOfClass:[XX_ViewController class]]) {//return;}}转载于:https://www.cnblogs.com/mapanguan/p/5853986.html

eureka 其它语言_SpringCloud之Eureka-Go语言中文社区

一、使用方法:1、添加maven依赖org.springframework.cloudspring-cloud-starter-netflix-eureka-server版本一般交由spring-cloud-dependencies管理。注意这个依赖的artifactId在Edgware以前是spring-cloud-starter-eureka-server&#xff0c;而在之后变成了spring-cloud-start…

操作系统:Win10系统下LocalNow和Roaming文件夹介绍

Win10操作系统下AppData文件夹包括以下子文件夹 - 漫游&#xff0c;本地和本地。 几乎每个在Win10 PC上安装的程序都会在AppData文件夹中创建自己的文件夹&#xff0c;并将其所有相关信息存储在其中。AppData或应用程序数据是Windows 10中的一个隐藏文件夹&#xff0c;可帮助保…

c语言des算法实验报告,C语言实现DES算法实验报告解析.doc

C语言实现DES算法实验报告解析xx工程大学实验报告(2015-2016学年第一学期)报告题目&#xff1a; DES加密算法课程名称&#xff1a; 密码学B任课教员&#xff1a;专 业&#xff1a;学 号&#xff1a;姓 名&#xff1a;二O一六年一月十八日一、课程概述目的&#xff1a;培养学员的…

[noip2010]关押罪犯 并查集

第一次看的时候想到了并查集&#xff0c;但是不知道怎么实现&#xff1b; 标解&#xff0c;f[i]表示i所属的集合&#xff0c;用f[in]表示i所属集合的补集&#xff0c;实现的很巧妙&#xff0c;可以当成一个使用并查集的巧妙应用&#xff1b; 1 #include<iostream>2 #incl…

jvm什么是本地方法

一&#xff1a;什么是本地方法 二&#xff1a;举例 三&#xff1a;为什么要使用Native Method

SQLServer:用户自定义数据类型用法

今天给大家梳理一下SQLServer:用户自定义数据类型用法&#xff0c;希望对大家能有所帮助&#xff01;1、基于基本数据类型创建的别名数据类型-- 创建生日的数据类型 CREATE TYPE birthday FROM datetime NULL; -- 创建用户表 CREATE TABLE userInfo (id varchar(32), userNam…

python fsolve说明_Python fsolve()抱怨形状.为什么?

具有函数f(x,y,z),我需要解决限制f(x,y,z) 0然后绘制它.我试图为每对(y,z)找到f(x,y,z) 0的值x&#xff1a;from numpy import *from scipy.optimize import fsolvedef func(x,y,z):return xyzy linspace(0,1,100)z linspace(0,1,100)x0 zeros((y.size,z.size)) 0.5 # the …

C语言实现与功能的程序,用C语言实现Ping程序功能

2001 年 10 月 01 日大部分人用ping命令只是作为查看另一个系统的网络连接是否正常的一种简单方法。在这篇文章中&#xff0c;作者将介绍如何用C语言编写一个模拟ping命令功能的程序。ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理…

数据库知识:SQLServer变量相关知识介绍

今天给大家分享SQLServer变量相关介绍&#xff0c;希望对大家能有所帮助&#xff01;1、概述SQLServer变量对应内存中的一个存储空间。它和常量不同&#xff0c;变量的值可以在执行过程中改变。2、分类SQLServer变量根据作用范围不同主要分为局部变量和全局变量。2.1.局部变量局…