Test
题意:如果区间 [L,R] 里的所有元素(即此排列的第 L个到第 R 个元素)递增排序后能得到一个长度为 R−L+1 的“连续”数列,则称这个区间连号区间 换句话说 也就是对于区间[l,r] 因为这个序列是排列 也就是此区间的即区间长度
不难想到暴力解法 对于每个i去枚举以i开始的所有满足区间的个数 时间复杂度O(n²) 包TLE的
利用扫描线算法枚举右端点R找出以R为结尾符合要求的数,累加即可 现在我们看怎么满足条件 首先我们知道 要动态维护一段区间的最值单调栈无非是很简单就可以实现的(ps:也可以ST表 想用自行食用) 我们通过两个单调栈分别维护[l-r]的最大值和最小值即可 那么就有max-min=r-l 因为我们枚举的r 即有max-min+l=r 用线段树找到max-min-l最小值的个数求和即可
公式推导
\[\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i +l= r
\]
\[\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i 可以理解为[l,r]内元素的极差\\ 一个排列在[l,r]这个区间内极差至少是r-l \\所以可得出\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i >=r-l\\ 即:\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i +l>=r
\]
为什么统计最小值的个数而不是统计zero的个数
\[然而我们的目标是找到满足 \max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r的区间[l,r] 我们用线段树来维护以r为右端点\\对于所有可能区间 [l,r]的\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l的值因为\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l>=r\\那么就有\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r的情况就是\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r时(最小值为r时)\\综上所述我们只需要统计\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l的最小值个数即为右端点为r时满足条件的区间个数
\]
如何维护单调递增栈?有什么用?
Code:
while (!st1.empty() && a[i] < a[st1.top()]) //该条件表明当栈 st1 不为空,并且当前元素 a[i] 小于栈顶元素 a[st1.top()] 时,进入循环。其目的在于保证栈 st1 中的元素所对应数组值是递增的。
{
// 修改区间 [st1.top() - 1 + 1, p - 1] 的值,更新最小值
int pp = st1.top(); //将栈顶元素的值保存到变量 prev 中,然后把栈顶元素从栈 st1 中弹出。 因为后续需要用这个数值来该确定修改的数值
st1.pop();
int q = st1.empty() ? 0 : st1.top();//获取当前栈顶元素的前一个元素。若栈为空,就将 prevTop 设为 0;反之,则设为当前栈顶元素的值。
modify(1, q + 1, p - 1, a[pp] - a[i]);
p = pp;//把 p 更新为刚刚弹出的栈顶元素的值,为下一次区间修改做准备。
}
用于维护以 i 为右端点的区间内的最小值。当 a[i] 小于栈顶元素时,说明栈顶元素不再是后续区间的最小值,需要更新线段树中相应区间的 max - min - l 的值。 举个例子:假设有数组 a = [3, 1, 2],当前枚举到 i = 1,a[1] = 1此时单调递增栈 st1里有元素 3(栈顶)。由于 a[1] = 1 < 3,这就表明 3不再是后续以 1 为右端点的区间内的最小值了,
对 max - min - l的影响
当栈顶元素不再是最小值时,对于那些包含栈顶元素但不包含当前元素 a[i] 的区间,它们的 min 值会发生改变。因为 min 值变小了,所以 max - min - l的值也会相应改变。例如,考虑区间 [l, r],原本 min 是栈顶元素 a[st1.top()],现在新的 min 是 a[i]。那么 max - min - l 的变化量就是 a[st1.top()] - a[i]。即:
#### 不难想到暴力解法 对于每个i去枚举以i开始的所有满足区间的个数 时间复杂度O(n²) 包TLE的
### 利用扫描线算法枚举右端点R找出以R为结尾符合要求的数,累加即可 现在我们看怎么满足条件 首先我们知道 要动态维护一段区间的最值单调栈无非是很简单就可以实现的(ps:也可以ST表 想用自行食用) 我们通过两个单调栈分别维护[l-r]的最大值和最小值即可 那么就有max-min=r-l 因为我们枚举的r 即有max-min+l=r 用线段树找到max-min-l最小值的个数求和即可
# 公式推导
$$
\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i +l= r
$$
$$
\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i 可以理解为[l,r]内元素的极差\\ 一个排列在[l,r]这个区间内极差至少是r-l \\所以可得出\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i >=r-l\\ 即:\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i +l>=r
$$
## 为什么统计最小值的个数而不是统计zero的个数
$$
然而我们的目标是找到满足 \max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r的区间[l,r] 我们用线段树来维护以r为右端点\\对于所有可能区间 [l,r]的\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l的值因为\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l>=r\\那么就有\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r的情况就是\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l=r时(最小值为r时)\\综上所述我们只需要统计\max_{l\leq i\leq r} p_i - \min_{l\leq i\leq r} p_i+l的最小值个数即为右端点为r时满足条件的区间个数
$$
## 如何维护单调递增栈?有什么用?
## Code:
```c++
while (!st1.empty() && a[i] < a[st1.top()]) //该条件表明当栈 st1 不为空,并且当前元素 a[i] 小于栈顶元素 a[st1.top()] 时,进入循环。其目的在于保证栈 st1 中的元素所对应数组值是递增的。
{
// 修改区间 [st1.top() - 1 + 1, p - 1] 的值,更新最小值
int pp = st1.top(); //将栈顶元素的值保存到变量 prev 中,然后把栈顶元素从栈 st1 中弹出。 因为后续需要用这个数值来该确定修改的数值
st1.pop();
int q = st1.empty() ? 0 : st1.top();//获取当前栈顶元素的前一个元素。若栈为空,就将 prevTop 设为 0;反之,则设为当前栈顶元素的值。
modify(1, q + 1, p - 1, a[pp] - a[i]);
p = pp;//把 p 更新为刚刚弹出的栈顶元素的值,为下一次区间修改做准备。
}
用于维护以 i 为右端点的区间内的最小值。当 a[i] 小于栈顶元素时,说明栈顶元素不再是后续区间的最小值,需要更新线段树中相应区间的 max - min - l 的值。 举个例子:假设有数组 a = [3, 1, 2],当前枚举到 i = 1,a[1] = 1此时单调递增栈 st1里有元素 3(栈顶)。由于 a[1] = 1 < 3,这就表明 3不再是后续以 1 为右端点的区间内的最小值了,
对 max - min - l的影响
当栈顶元素不再是最小值时,对于那些包含栈顶元素但不包含当前元素 a[i] 的区间,它们的 min 值会发生改变。因为 min 值变小了,所以 max - min - l的值也会相应改变。例如,考虑区间 [l, r],原本 min 是栈顶元素 a[st1.top()],现在新的 min 是 a[i]。那么 max - min - l 的变化量就是 a[st1.top()] - a[i]。即:
modify(1, pp + 1, p - 1, a[p] - a[i]);
单调递减栈类似 ... 只不过变一下需要修改的数值即可
贴个code:
while (!st2.empty() && a[i] > a[st2.top()])
{
// 修改区间 [st2.top() - 1 + 1, p - 1] 的值,更新最小值
int pp = st2.top();
st2.pop();
int q = st2.empty() ? 0 : st2.top();
modify(1, q + 1, p - 1, -a[pp] + a[i]);
p = pp;
}
线段树操作 若没学过或不熟练 食用这个OI wiki
线段树节点信息:维护最小值即 minv=max-min-l 以及最小值出现的次数 cnt 和 懒标记:tag 注:线段树的灵魂 用于修改查询等传递
struct Info
{
int cnt;
int minv;
int tag;
Info() : cnt(0), minv(0), tag(0) {}
Info(int cnt, int minv, int tag) : cnt(cnt), minv(minv), tag(tag) {}
Info operator+(const Info &a) const
{
Info res;
res.minv = min(minv, a.minv);
res.cnt = (res.minv == minv ? cnt : 0) + (res.minv == a.minv ? a.cnt : 0);
return res;
}
};
建树过程也就是分别递归左右子树 到叶节点return 就行了
void Build(int id, int l, int r)
{
t[id].tag = 0;
if (l == r)
{
t[id].minv = l;
t[id].cnt = 1;
return;
}
int mid = (l + r) >> 1;
Build(id << 1, l, mid);
Build(id << 1 | 1, mid + 1, r);
Push_Up(id);
}
子节点传叶节点操作 pushup操作
void Push_Up(int id)
{
t[id] = t[id << 1] + t[id << 1 | 1];
}
父节点懒标记传给子节点 pushdown 操作
void Push_Down(int id)
{
if (t[id].tag)
{
t[id << 1].tag += t[id].tag;
t[id << 1 | 1].tag += t[id].tag;
t[id << 1].minv += t[id].tag;
t[id << 1 | 1].minv += t[id].tag;
t[id].tag = 0;
}
}
区间修改 如果覆盖整个区间对懒标记加上 最小值也加上 否则根据中点 判断递归左右子树就行
void modify(int id, int l, int r, int cnt, int L, int R)
{
if (l <= L && R <= r)
{
t[id].tag += cnt;
t[id].minv += cnt;
return;
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
modify(id << 1, l, r, cnt, L, mid);
else if (l > mid)
modify(id << 1 | 1, l, r, cnt, mid + 1, R);
else
{
modify(id << 1, l, mid, cnt, L, mid);
modify(id << 1 | 1, mid + 1, r, cnt, mid + 1, R);
}
Push_Up(id);
}
查询操作 和修改类似
Info Query(int id, int l, int r, int L, int R)
{
if (l <= L && R <= r)
{
return t[id];
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
return Query(id << 1, l, r, L, mid);
else if (l > mid)
return Query(id << 1 | 1, l, r, mid + 1, R);
else
return Query(id << 1, l, mid, L, mid) + Query(id << 1 | 1, mid + 1, r, mid + 1, R);
}
然后就emm... 操作就行了 🤔🤔 然后就AC了(๑•̀ㅂ•́)و✧
Code:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define sz(x) (int)x.size()
#define all(v) v.begin(), v.end()
#define ps(y) cout << fixed << setprecision(y)
/*
/\_/\
( o.o ) /\_/\
> ^ < ( o.o )
> ^ <
/\_/\ /\_/\
( o.o ) ( o.o )
> ^ < > ^ <
*/
struct Info
{
int cnt;
int minv;
int tag;
Info() : cnt(0), minv(0), tag(0) {}
Info(int cnt, int minv, int tag) : cnt(cnt), minv(minv), tag(tag) {}
Info operator+(const Info &a) const
{
Info res;
res.minv = min(minv, a.minv);
res.cnt = (res.minv == minv ? cnt : 0) + (res.minv == a.minv ? a.cnt : 0);
return res;
}
};
struct SegTree
{
int n;
vector<Info> t;
SegTree(int n) : n(n), t(4 * n)
{
Build(1, 1, n);
}
void Build(int id, int l, int r)
{
t[id].tag = 0;
if (l == r)
{
t[id].minv = l;
t[id].cnt = 1;
return;
}
int mid = (l + r) >> 1;
Build(id << 1, l, mid);
Build(id << 1 | 1, mid + 1, r);
Push_Up(id);
}
void Push_Up(int id)
{
t[id] = t[id << 1] + t[id << 1 | 1];
}
void Push_Down(int id)
{
if (t[id].tag)
{
t[id << 1].tag += t[id].tag;
t[id << 1 | 1].tag += t[id].tag;
t[id << 1].minv += t[id].tag;
t[id << 1 | 1].minv += t[id].tag;
t[id].tag = 0;
}
}
void modify(int id, int l, int r, int cnt, int L, int R)
{
if (l <= L && R <= r)
{
t[id].tag += cnt;
t[id].minv += cnt;
return;
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
modify(id << 1, l, r, cnt, L, mid);
else if (l > mid)
modify(id << 1 | 1, l, r, cnt, mid + 1, R);
else
{
modify(id << 1, l, mid, cnt, L, mid);
modify(id << 1 | 1, mid + 1, r, cnt, mid + 1, R);
}
Push_Up(id);
}
Info Query(int id, int l, int r, int L, int R)
{
if (l <= L && R <= r)
{
return t[id];
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
return Query(id << 1, l, r, L, mid);
else if (l > mid)
return Query(id << 1 | 1, l, r, mid + 1, R);
else
return Query(id << 1, l, mid, L, mid) + Query(id << 1 | 1, mid + 1, r, mid + 1, R);
}
};
int n;
int a[300050];
i64 ans;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
SegTree seg(n);
stack<int> st1, st2;
for (int i = 1; i <= n; i++)
{
int p = i;
while (!st1.empty() && a[i] < a[st1.top()])
{
int pp = st1.top();
st1.pop();
int q = st1.empty() ? 0 : st1.top();
seg.modify(1, q + 1, p - 1, a[pp] - a[i], 1, n);
p = pp;
}
p = i;
while (!st2.empty() && a[i] > a[st2.top()])
{
int pp = st2.top();
st2.pop();
int q = st2.empty() ? 0 : st2.top();
seg.modify(1, q + 1, p - 1, -a[pp] + a[i], 1, n);
p = pp;
}
st1.push(i);
st2.push(i);
ans += seg.Query(1, 1, n, 1, n).cnt;
}
cout << ans << endl;
return 0;
}
时间复杂度分析:循环遍历每一个元素O(n) 循环内线段树修改 因为是每次分左右 也就是以2的倍数去分的 所以修改的时间复杂度是O(logn) 总时间复杂度O(nlogn)
法二:分治 分max在左边 然后在右边找满足条件的最大 和 max在右边 在左边找满足条件的最小 否则递归下一层左边和右边 统计答案贡献 一直递归小心爆栈 这个递归栈的深度为 O(logn) 显然不会 . (((φ(◎ロ◎;)φ)))
modify(1, pp + 1, p - 1, a[p] - a[i]);
## 单调递减栈类似 ... 只不过变一下需要修改的数值即可
### 贴个code:
```c++
while (!st2.empty() && a[i] > a[st2.top()])
{
// 修改区间 [st2.top() - 1 + 1, p - 1] 的值,更新最小值
int pp = st2.top();
st2.pop();
int q = st2.empty() ? 0 : st2.top();
modify(1, q + 1, p - 1, -a[pp] + a[i]);
p = pp;
}
线段树操作 若没学过或不熟练 食用这个OI wiki
线段树节点信息:维护最小值即 minv=max-min-l 以及最小值出现的次数 cnt 和 懒标记:tag 注:线段树的灵魂 用于修改查询等传递
struct Info
{
int cnt;
int minv;
int tag;
Info() : cnt(0), minv(0), tag(0) {}
Info(int cnt, int minv, int tag) : cnt(cnt), minv(minv), tag(tag) {}
Info operator+(const Info &a) const
{
Info res;
res.minv = min(minv, a.minv);
res.cnt = (res.minv == minv ? cnt : 0) + (res.minv == a.minv ? a.cnt : 0);
return res;
}
};
建树过程也就是分别递归左右子树 到叶节点return 就行了
void Build(int id, int l, int r)
{
t[id].tag = 0;
if (l == r)
{
t[id].minv = l;
t[id].cnt = 1;
return;
}
int mid = (l + r) >> 1;
Build(id << 1, l, mid);
Build(id << 1 | 1, mid + 1, r);
Push_Up(id);
}
子节点传叶节点操作 pushup操作
void Push_Up(int id)
{
t[id] = t[id << 1] + t[id << 1 | 1];
}
父节点懒标记传给子节点 pushdown 操作
void Push_Down(int id)
{
if (t[id].tag)
{
t[id << 1].tag += t[id].tag;
t[id << 1 | 1].tag += t[id].tag;
t[id << 1].minv += t[id].tag;
t[id << 1 | 1].minv += t[id].tag;
t[id].tag = 0;
}
}
区间修改 如果覆盖整个区间对懒标记加上 最小值也加上 否则根据中点 判断递归左右子树就行
void modify(int id, int l, int r, int cnt, int L, int R)
{
if (l <= L && R <= r)
{
t[id].tag += cnt;
t[id].minv += cnt;
return;
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
modify(id << 1, l, r, cnt, L, mid);
else if (l > mid)
modify(id << 1 | 1, l, r, cnt, mid + 1, R);
else
{
modify(id << 1, l, mid, cnt, L, mid);
modify(id << 1 | 1, mid + 1, r, cnt, mid + 1, R);
}
Push_Up(id);
}
查询操作 和修改类似
Info Query(int id, int l, int r, int L, int R)
{
if (l <= L && R <= r)
{
return t[id];
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
return Query(id << 1, l, r, L, mid);
else if (l > mid)
return Query(id << 1 | 1, l, r, mid + 1, R);
else
return Query(id << 1, l, mid, L, mid) + Query(id << 1 | 1, mid + 1, r, mid + 1, R);
}
然后就emm... 操作就行了 🤔🤔 然后就AC了(๑•̀ㅂ•́)و✧
Code:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define sz(x) (int)x.size()
#define all(v) v.begin(), v.end()
#define ps(y) cout << fixed << setprecision(y)
/*
/\_/\
( o.o ) /\_/\
> ^ < ( o.o )
> ^ <
/\_/\ /\_/\
( o.o ) ( o.o )
> ^ < > ^ <
*/
struct Info
{
int cnt;
int minv;
int tag;
Info() : cnt(0), minv(0), tag(0) {}
Info(int cnt, int minv, int tag) : cnt(cnt), minv(minv), tag(tag) {}
Info operator+(const Info &a) const
{
Info res;
res.minv = min(minv, a.minv);
res.cnt = (res.minv == minv ? cnt : 0) + (res.minv == a.minv ? a.cnt : 0);
return res;
}
};
struct SegTree
{
int n;
vector<Info> t;
SegTree(int n) : n(n), t(4 * n)
{
Build(1, 1, n);
}
void Build(int id, int l, int r)
{
t[id].tag = 0;
if (l == r)
{
t[id].minv = l;
t[id].cnt = 1;
return;
}
int mid = (l + r) >> 1;
Build(id << 1, l, mid);
Build(id << 1 | 1, mid + 1, r);
Push_Up(id);
}
void Push_Up(int id)
{
t[id] = t[id << 1] + t[id << 1 | 1];
}
void Push_Down(int id)
{
if (t[id].tag)
{
t[id << 1].tag += t[id].tag;
t[id << 1 | 1].tag += t[id].tag;
t[id << 1].minv += t[id].tag;
t[id << 1 | 1].minv += t[id].tag;
t[id].tag = 0;
}
}
void modify(int id, int l, int r, int cnt, int L, int R)
{
if (l <= L && R <= r)
{
t[id].tag += cnt;
t[id].minv += cnt;
return;
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
modify(id << 1, l, r, cnt, L, mid);
else if (l > mid)
modify(id << 1 | 1, l, r, cnt, mid + 1, R);
else
{
modify(id << 1, l, mid, cnt, L, mid);
modify(id << 1 | 1, mid + 1, r, cnt, mid + 1, R);
}
Push_Up(id);
}
Info Query(int id, int l, int r, int L, int R)
{
if (l <= L && R <= r)
{
return t[id];
}
Push_Down(id);
int mid = (L + R) >> 1;
if (r <= mid)
return Query(id << 1, l, r, L, mid);
else if (l > mid)
return Query(id << 1 | 1, l, r, mid + 1, R);
else
return Query(id << 1, l, mid, L, mid) + Query(id << 1 | 1, mid + 1, r, mid + 1, R);
}
};
int n;
int a[300050];
i64 ans;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
SegTree seg(n);
stack<int> st1, st2;
for (int i = 1; i <= n; i++)
{
int p = i;
while (!st1.empty() && a[i] < a[st1.top()])
{
int pp = st1.top();
st1.pop();
int q = st1.empty() ? 0 : st1.top();
seg.modify(1, q + 1, p - 1, a[pp] - a[i], 1, n);
p = pp;
}
p = i;
while (!st2.empty() && a[i] > a[st2.top()])
{
int pp = st2.top();
st2.pop();
int q = st2.empty() ? 0 : st2.top();
seg.modify(1, q + 1, p - 1, -a[pp] + a[i], 1, n);
p = pp;
}
st1.push(i);
st2.push(i);
ans += seg.Query(1, 1, n, 1, n).cnt;
}
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号