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 ...
随机推荐
- wifi认证Portal开发系列(三):portal协议
中国移动WLAN业务PORTAL协议规范介绍 一.用户上线认证流程 上线流程完成用户账号的认证,并把认证结果通知Portal Server,Portal server将会通知WLAN用户并且显示相应的 ...
- python学习(三)数字类型示例
奶奶的报了这个错,我以为可以像java中字符串加数字的嘛 Traceback (most recent call last): File "./number.py", line ...
- 未加载Microsoft.SqlServer.management.sdk.sfc version......
这个问题卡了我好久,于是决定记录下来,我这里缺失的是Microsoft.SqlServer.management.sdk.sfc version 12.0.0,当然你也可能后面是11开头的, 这个是由 ...
- Linux 批量替换的一种实现方式
替换某目录下所有文件中的某个字符: sed -i 's/origin_str/new_str/g' `grep origin_str -rl ./` origin_str:被替换的字符串: new_s ...
- linux 时间格式
版权为个人所有,欢迎转载如转载请说明出处.(东北大亨) http://www.cnblogs.com/northeastTycoon/p/5511718.html 时间域 % H 小时(00..23) ...
- 关于TextView 的属性
一.设置不同的字体和颜色值:questionDesTextView=(TextView)findViewById(R.id.question_des); SpannableStringBuilder ...
- 关于Android6.0 之EasyPermissionUtil
之前6.0权限用第三方类库比较多,但是都是挺麻烦的,今天给大家推荐一个好用的第三方类库: gitHub地址:https://github.com/yxping/EasyPermissionUtil 使 ...
- iPhone,iPad如何获取WIFI名称即SSID
本文转载至 http://blog.csdn.net/wbw1985/article/details/20530281 2010年开始苹果清理了一批APP Store上的WIFI扫描软件, 缘由语焉 ...
- python 基础 8.3 match方法和search方法
一,正则对象的split 方法 split(string[,maxsplit]) 按照能够匹配的字串讲string 分割后返回列表.maxsplit 用于指定最大分割次数,不指定将全部分割.来查找符合 ...
- Java 8 default 函数
我们知道在java8之前 ,一个类实现一个接口需要实现接口所有的方法, 但是这样会导致一个问题,当一个接口有很多的实现类的时候,修改这个接口就变成了一个非常麻烦的事,需要修改这个接口的所有实现类 不过 ...