ACM学习历程—HDU 5289 Assignment(线段树 || RMQ || 单调队列)
First Sample, the satisfied groups include:[1,1]、[2,2]、[3,3]、[4,4] 、[2,3]
题目大意是求满足下列条件的子区间的个数:
对于子区间[L, R]内的任意两个元素的差值小于k。
大概有以下三种方法:
第一种:(线段树)
首先可以肯定的是,以An起始的区间肯定是[n, n]。
然后以An-1起始的区间最长是[n-1, n],然后考虑需不需要把区间的右值减小,也就是考虑An和An-1的差值是否小于k。假设最终的区间为[n-1, d(n-1)]。
于是对于以An-2起始的区间,自然最长是[n-2, d(n-1)],然后考虑需不需要把区间的右值减小,也就是考虑这个区间内是否存在某个值与An-2的差值大于等于k。
以此类推,以Ai起始的区间应为[i, min(d(i+1), p)],其中p是i右侧最后一个满足与Ai差值小于k的数的脚标。
于是采用线段树记录区间的最大值和最小值,就能查询出任意[i, n]区间里第一个满足与Ai差值大于等于k的值的位置x,然后x-1即为最后一个满足与Ai差值小于k的数的脚标。
(此处采用ans记录x,ans为-1表示没找到,自然x-1就是n)
复杂度:n*logn(枚举左端点*查询右端点)
代码:(线段树)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <string>
#include <algorithm>
#define LL long long using namespace std; int n, k;
int a[], ans;
int d[]; //线段树
//区间每点增值,求区间和
const int maxn = ;
struct node
{
int lt, rt;
LL mi, ma;
}tree[*maxn]; //向上更新
void pushUp(int id)
{
tree[id].mi = min(tree[id<<].mi, tree[id<<|].mi);
tree[id].ma = max(tree[id<<].ma, tree[id<<|].ma);
} //建立线段树
void build(int lt, int rt, int id)
{
tree[id].lt = lt;
tree[id].rt = rt;
tree[id].mi = ;//每段的初值,根据题目要求
tree[id].ma = ;
if (lt == rt)
{
tree[id].mi = tree[id].ma = a[lt];
return;
}
int mid = (lt + rt) >> ;
build(lt, mid, id<<);
build(mid+, rt, id<<|);
pushUp(id);
} void query(int lt, int rt, int id, int v)
{
if (tree[id].lt == tree[id].rt)
{ if (abs(tree[id].mi-v) >= k)
{
if (ans == - || ans > tree[id].lt)
ans = tree[id].lt;
}
return;
}
int mid = (tree[id].lt + tree[id].rt) >> ;
if (lt <= mid)
if (abs(tree[id<<].mi-v) >= k || abs(tree[id<<].ma-v) >= k)
query(lt, rt, id<<, v);
if (ans == - && rt > mid)
if (abs(tree[id<<|].mi-v) >= k || abs(tree[id<<|].ma-v) >= k)
query(lt, rt, id<<|, v);
} void input()
{
scanf("%d%d", &n, &k);
for (int i = ; i <= n; ++i)
{
scanf("%d", &a[i]);
}
build(, n, );
} void work()
{
LL sum = ;
d[n] = n;
for (int i = n-; i >= ; --i)
{
ans = -;
query(i, n, , a[i]);
if (ans != -)
ans -= ;
else
ans = n; d[i] = min(ans, d[i+]);
sum += d[i]-i+;
}
printf("%lld\n", sum);
} int main()
{
//freopen("test.txt", "r", stdin);
int T;
scanf("%d", &T);
for (int times = ; times < T; ++times)
{
input();
work();
}
return ;
}
第二种:(RMQ+二分区间长度)
使用RMQ可以对于无修改操作的任意区间的最值进行查询。
这样就可以枚左端点,然后二分区间长度得到右端点(此处直接二分了右端点的位置)。
第一次使用RMQ,一个小错误找了很久。
当然此处仍可以用线段树维护最值,但是效率有损。
复杂度:n*logn*logn(枚举左端点*二分右端点*RMQ查询时得到的区间长度的log)
代码:(RMQ)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <set>
#include <map>
#include <string>
#include <queue>
#include <vector>
#define LL long long using namespace std; const int maxN = ; int n, k;
int a[maxN];
int mi[maxN][], ma[maxN][]; void RMQ()
{
for (int i = ; i < n; ++i)
mi[i][] = ma[i][] = a[i];
for (int j = ; (<<j) <= n; ++j)
for (int i = ; i+(<<j)- < n; ++i)
{
mi[i][j] = min(mi[i][j-], mi[i+(<<(j-))][j-]);
ma[i][j] = max(ma[i][j-], ma[i+(<<(j-))][j-]);
} } int query(int lt, int rt)
{
int k = ;
while ((<<(k+)) <= rt-lt+)
k++;
return max(ma[lt][k], ma[rt-(<<k)+][k]) - min(mi[lt][k], mi[rt-(<<k)+][k]);
} int binarySearch(int from)
{
int lt = from, rt = n-, mid;
while (lt+ < rt)
{
mid = (lt+rt)>>;
if (query(from, mid) >= k)
rt = mid;
else
lt = mid;
}
if (query(from, rt) < k)
return rt;
else
return lt;
} void input()
{
scanf("%d%d", &n, &k);
for (int i = ; i < n; ++i)
scanf("%d", &a[i]);
RMQ();
} void work()
{
LL ans = ;
int to;
for (int i = ; i < n; ++i)
{
to = binarySearch(i);
ans += to-i+;
}
printf("%lld\n", ans);
} int main()
{
//freopen("test.in", "r", stdin);
int T;
scanf("%d", &T);
for (int times = ; times < T; ++times)
{
input();
work();
}
return ;
}
第三种:(单调队列)
这个问题由于之前线段树是枚举左端点,然后找最大的满足条件的右端点,即右侧首个不满足条件的点的左侧一个点。
重点是左端点是从大到小取的。这样才能保证左端点变小以后,其不包含左端点的子区间也能满足要求。显然,不包含当前左端点的子区间就是上一次满足条件的最大区间的子区间。
如果从小到大取左端点。那么必然新增的点都在右端,然而并不能保证这右边新增的点与原来的点满足条件。
可以对左端点枚举的话,同理可以对右端点枚举。这里为了看起来方便一点,就对枚举右端点的情况进行考虑。
自然思路还是一样的,枚举右端点,然后找满足条件的最小的右端点,自然跟线段树的做法一样,是考虑上一次满足条件的区间,加入新的右端点后考虑需要排掉多少左端的点。
到这里就是需要维护当前区间最值,可以使用两个优先队列一个维护当前队列的最小值,一个维护最大值;然后对于一个队列,把在区间外的值直接弹出容器,然后把不满足条件的值弹出容器,维护弹出的脚标的最大值。然后优先队列取出来的值取较大的。
这样的话效率是nlogn。但是会发现,对于区间外的值其实好多是没必要进队列的。而且对于一个这样的数列,如图
对于某个右端点来说,对于它前面上下波动k范围内的点,左端点肯定是取从左往右最后一个不满足条件的的右边一个点(当都满足取第一个)。
这样对于某两个不满足条件的点中间的一些满足条件的点自然是不需要进队的。
此外就是可以显而易见的,前一个区间加入新的右端点后,左端点只会向右移动或者不动,有了这个前提就可以实施上述方案了。
然后就可以维护两个单调的队列,一个是递增的,一个是递减的。即一个维护上界,一个维护下届的。
这样就和线段树一样,判断上界和下界是否和a[i]差值大于等于k,否则需要把区间缩小。(这里需要注意的是两个队列只有一开始是空的,后面至少有一个元素)
然后就是维护单调性:
对于递增的那个队列来说,当新的右端点加入后,如果右端点大于队列里面所有的数,自然直接进队,如果小于队列末端的点就弹出右端的点直到满足第一个条件。递减的类似。这样对于递增的队列来说,两个低点中间的高点没有进队列。对于递减队列来说,两个高点中间的低点没有进队列.
这样每个元素最多进一次队列,整体效率是O(n)的。
代码:(单调队列)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <deque>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#define LL long long using namespace std; const int maxN = ;
int n, k, a[maxN];
LL ans; void input()
{
scanf("%d%d", &n, &k);
for (int i = ; i < n; ++i)
scanf("%d", &a[i]);
} void work()
{
if (k == )
{
printf("0\n");
return;
}
ans = ;
deque<int> mi, ma;
int p = ;
for (int i = ; i < n; ++i)
{
while (!(mi.empty() && ma.empty()) &&
!(abs(a[i]-a[mi.front()]) < k && abs(a[i]-a[ma.front()]) < k))
{
p++;
while (!mi.empty() && mi.front() < p)
mi.pop_front();
while (!ma.empty() && ma.front() < p)
ma.pop_front();
}
ans += i-p+;
while (!mi.empty() && a[mi.back()] > a[i])
mi.pop_back();
mi.push_back(i);
while (!ma.empty() && a[ma.back()] < a[i])
ma.pop_back();
ma.push_back(i);
}
printf("%lld\n", ans);
} int main()
{
//freopen("test.in", "r", stdin);
int T;
scanf("%d", &T);
for (int times = ; times < T; ++times)
{
input();
work();
}
return ;
}
ACM学习历程—HDU 5289 Assignment(线段树 || RMQ || 单调队列)的更多相关文章
- ACM学习历程—HDU 2795 Billboard(线段树)
Description At the entrance to the university, there is a huge rectangular billboard of size h*w (h ...
- PKU 2823 Sliding Window(线段树||RMQ||单调队列)
题目大意:原题链接(定长区间求最值) 给定长为n的数组,求出每k个数之间的最小/大值. 解法一:线段树 segtree节点存储区间的最小/大值 Query_min(int p,int l,int r, ...
- ACM学习历程—HDU 5443 The Water Problem(RMQ)(2015长春网赛1007题)
Problem Description In Land waterless, water is a very limited resource. People always fight for the ...
- bzoj 1171 并查集优化顺序枚举 | 线段树套单调队列
详见vfleaking在discuss里的题解. 收获: 当我们要顺序枚举一个序列,并且跳过某些元素,那么我们可以用并查集将要跳过的元素合并到一起,这样当一长串元素需要跳过时,可以O(1)跳过. 暴力 ...
- 1304F2 - Animal Observation (hard version) 线段树or单调队列 +DP
1304F2 - Animal Observation (hard version) 线段树or单调队列 +DP 题意 用摄像机观察动物,有两个摄像机,一个可以放在奇数天,一个可以放在偶数天.摄像机在 ...
- ACM学习历程—HDU 5023 A Corrupt Mayor's Performance Art(广州赛区网赛)(线段树)
Problem Description Corrupt governors always find ways to get dirty money. Paint something, then sel ...
- ACM学习历程—HDU5696 区间的价值(分治 && RMQ && 线段树 && 动态规划)
http://acm.hdu.edu.cn/showproblem.php?pid=5696 这是这次百度之星初赛2B的第一题,但是由于正好打省赛,于是便错过了.加上2A的时候差了一题,当时有思路,但 ...
- ACM学习笔记:可持久化线段树
title : 可持久化线段树 date : 2021-8-18 tags : 数据结构,ACM 可持久化线段树 可以用来解决线段树存储历史状态的问题. 我们在进行单点修改后,线段树只有logn个(一 ...
- ACM学习历程—HDU 5317 RGCDQ (数论)
Problem Description Mr. Hdu is interested in Greatest Common Divisor (GCD). He wants to find more an ...
随机推荐
- Hibernate学习二----------hibernate简介
© 版权声明:本文为博主原创文章,转载请注明出处 1.hibernate.cfg.xml常用配置 - hibernate.show_sql:是否把Hibernate运行时的SQL语句输出到控制台,编码 ...
- wifi认证Portal开发系列(二):FreeRadius的安装和测试、关联Mysql
注:本次安装是基于FreeRadius 3版本进行安装配置的,在配置Mysql的过程中,与2版本有些不同.操作系统是CentOS 7 一.准备工作 工具的安装 #安装rz.sz命令用于文件上传 yum ...
- c# mvc 路由规则学习片段
1.初步接触mvc 路由 routes.MapRoute( "CM", "CM/{controller}/{act ...
- java中BigDecimal的学习
干着java的活,但是看的都是一些偏底层的东西(或者我根本就没有看),有点荒废了java的学习. 最近一直在用到一个类是BigDecimal,但都是模棱两可地在那儿用,并没有深入研究这个类的细节,感觉 ...
- gitlab多人协同工作
gitlab多人协同工作 本文为亨利向<Git权威指南>的作者蒋鑫老师的答疑邮件写成. 这里特别感谢蒋鑫老师对我询问gitlab的协同工作流程问题的详细解答. 蒋鑫老师的细致专业的解答让我 ...
- _DataStructure_C_Impl:Floyd算法求有向网N的各顶点v和w之间的最短路径
#include<stdio.h> #include<stdlib.h> #include<string.h> typedef char VertexType[4] ...
- C#与Java在修饰符上的不同
1.readonly 修饰符仅用于修饰类的数据成员.正如其名字说的,一旦它们已经进行了写操作.直接初始化或在构造函数中对其进行了赋值,数据成员就只能对其进行读取. readonly 和 const 数 ...
- spring整合hibernate,在获取sessionFactory的时候报错,求解决办法!!
applicationContext.xml文件 <!-- 开启扫包 --> <context:component-scan base-package="cn.edu&qu ...
- linux下压缩成zip文件解压zip文件
linux zip命令的基本用法是: zip [参数] [打包后的文件名] [打包的目录路径] linux zip命令参数列表: -a 将文件转成ASCII模式 -F 尝试修复损坏 ...
- python classmethod方法 和 staticmethod
classmethod() 是一个类方法,用来装饰对应的函数.被classmethod 装饰之后就无需实例化,也不需要在函数中传self,但是被装饰的函数第一个参数需要是cls来表示自身类.可以用来调 ...