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 ...
随机推荐
- maven打包时无法加载lib下的jar
© 版权声明:本文为博主原创文章,转载请注明出处 问题描述: 项目在本地部署没有问题,但是使用maven打包时报错: ***(引用jar中某个类的的路径) 不存在 ***(某个java类中的某行某列) ...
- ReactiveCocoa入门教程——第一部分【转载】
作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation).但是这些事件都用不同的方式来处理 ...
- 【Java】事件驱动模型和观察者模式
你有一件事情,做这件事情的过程包含了许多职责单一的子过程.这样的情况及其常见.当这些子过程有如下特点时,我们应该考虑设计一种合适的框架,让框架来完成一些业务无关的事情,从而使得各个子过程的开发可以专注 ...
- WARN util.NativeCodeLoader: Unable to load native-hadoop l... using builtin-java classes where applicable(附编译脚本)
WARN util.NativeCodeLoader: Unable to load native-hadoop l... using builtin-java classes where appli ...
- ubuntu系统安装好后一些基本软件的安装
preface: 由于某些原因重装了系统,原来在ubuntu下安装的一些软件又要又一次安装.稍微麻烦,整理下须要安装的步骤. #==================================== ...
- go web的简单服务器
1)简单web服务器: package main import ( "fmt" "net/http" ) func sayHelloName(w http.Re ...
- 【EDAS问题】轻量级EDAS部署hsf服务出现找不到类的解决方案
本地运行轻量级EDAS调用服务的时候报错如下: 2018-01-08 13:16:58.029 WARN [http-bio-8090-exec-8:t.hsf] [RPC Protocol call ...
- 转载 ---资深HR告诉你:我如何筛选简历与选择人员的
资深HR告诉你:我如何筛选简历与选择人员的 有个公司HR看简历 先直接丢掉一半 理由是不要运气不好的应聘者. 当然这可能只是某些HR面对太多的简历产生了偷懒的情绪,但是不论是Manager,亦或是 ...
- 为什么Java中的字符串是不可变的?
原文链接:https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ java字符串是不可变的.不可变类只是一个不能修改 ...
- Spring Boot 支持多种外部配置方式
Spring Boot 支持多种外部配置方式 http://blog.csdn.net/isea533/article/details/50281151 这些方式优先级如下: 命令行参数 来自java ...