title: 数据结构常见技巧、离线思想
tags: [线段树, 树状数组, 数据结构, 离线]
categories: [OI, 算法专题]
P4113 HEOI2012 采花
原题 · 标签:#树状数组 #线段树 #离线
考点
离线思想,疑似致敬HH的项链。
题意
- 有 \(n\) 朵花排成一排,共 \(c\) 种颜色。公主选一个区间 \([l, r]\) 采花,但要求采的花中每种颜色的花至少出现两次(否则该颜色不采)。
- \(m\) 次询问,每次给定区间 \([l, r]\),求该区间内有多少种颜色出现了至少两次。
数据范围:
- 子任务 1:\(1 \le n, c, m \le 3 \times 10^5\)。
- 子任务 2:\(1 \le n, c, m \le 2 \times 10^6\)。
- \(1 \le x_i \le c\),\(1 \le l \le r \le n\)。
思路
发现确定询问的右端点,只有从右往左数第二个元素是有效的,其他都不计入答案。
将询问离线。标记第二个元素很简单。每次询问都是寻找范围内有效元素的个数。
更新只是对有效元素的单点修改。树状数组解决就可以了。
代码
#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
struct BITadd {
int n;
vector <int> tree;
BITadd (int size) : n(size), tree(size + 1, 0) {}
void resize(int size) {
tree.resize(size + 1);
}
void clear() {
f(i, 1, n) tree[i] = 0;
}
int lowbit(int x) {
return x & (-x);
}
void modifyAdd(int x, int c) {
while (x <= n) {
tree[x] += c;
x += lowbit(x);
}
}
void modifySub(int x, int c) {
while (x <= n) {
tree[x] -= c;
x += lowbit(x);
}
}
int querySinT(int x) {
int res = 0;
while (x > 0) {
res += tree[x];
x -= lowbit(x);
}
return res;
}
int query(int l,int r){
return querySinT(r) - querySinT(l-1);
}
int querySin(int x){
return querySinT(x) - querySinT(x-1);
}
};
const int N = 2e6+10;
int n,c,m;
int x[N];
struct op{
int l,r,idx,ans;
bool operator < (op a){
return r < a.r;
}
}s[N];
bool cmp(op a,op b){
return a.idx < b.idx;
}
int last1[N],last2[N];
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> n >> c >> m;
BITadd bit(n+1);
f(i,1,n){
cin >> x[i];
}
f(i,1,m){
cin >> s[i].l >> s[i].r;
s[i].idx = i;
}
sort(s+1,s+1+m);
int idx = 1;
f(i,1,n){
if (!last1[x[i]]){ // 第一次出现
last1[x[i]] = i;
}
else{
if (last2[x[i]])bit.modifyAdd(last2[x[i]],-1);
last2[x[i]] = last1[x[i]];
last1[x[i]] = i;
bit.modifyAdd(last2[x[i]],1);
}
while (idx <= m && s[idx].r <= i){
s[idx].ans = bit.query(s[idx].l,s[idx].r);
idx++;
}
}
sort(s+1,s+1+m,cmp);
f(i,1,m){
cout << s[i].ans << "\n";
}
return 0;
}
CF2205D Simons and Beating Peaks
原题 · 标签:#笛卡尔树 #数据结构
考点
笛卡尔树
题意
- 给定一个长度为 \(n\) 的排列 \(a\)。称一个数组为"cool"当且仅当不存在下标 \(i\)(\(1 < i < m\))使得 \(b_i = \max(b_{i-1}, b_i, b_{i+1})\)(即没有局部严格最大值)。
- 每次操作:选择一个局部严格最大值 \(a_i\)(\(1 < i < n\)),然后删除其左邻居 \(a_{i-1}\) 或右邻居 \(a_{i+1}\),数组自动拼接。
- 重复操作直到数组变为 cool,求最少操作次数。
数据范围:
- \(1 \le t \le 5 \times 10^4\),\(3 \le n \le 5 \times 10^5\),\(\sum n \le 5 \times 10^5\)。
- \(a\) 是 \(1\) 到 \(n\) 的排列。
思路
思考一个区间如何划分为更小的问题:
肯定是大值左右两边都是小值,所以他才不是符合要求的。
考虑将最大值分别两侧的数清空,单独一次的代价就是清空的位数加上另外一侧区间的代价(更小规模的问题)。
考虑如果一个区间的长度为 \(0\),是满足要求的。如果其是一个区间的左右部分,处理后这个区间并不需要额外的操作,所以递归时其答案为 \(0\)。
这样的结构是笛卡尔树。处理时用单调栈扫一遍,以最大值为父节点就保持栈底是最大元素。最后一个出栈(如果有)的节点是当前节点的左儿子;当前节点是栈顶的右儿子。
代码
#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
int t;
int n;
vector <int> a,ls,rs;
pii dfs(int u){
if (!u){
return {0,0};
}
pii left = dfs(ls[u]),right = dfs(rs[u]);
return {left.first + right.first + 1,min(left.first + right.second,right.first + left.second)};
}
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> t;
while (t--){
cin >> n;
a.clear();ls.clear();rs.clear();
a.resize(n+1);ls.resize(n+1);rs.resize(n+1);
stack <int> stk;
f(i,1,n){
cin >> a[i];
}
int maxx = 0,maxxIdx = 0;
f(i,1,n){
int last = 0;
while (!stk.empty() && a[stk.top()] < a[i]){
last = stk.top();
stk.pop();
}
ls[i] = last;
if (!stk.empty()){
rs[stk.top()] = i;
}
stk.push(i);
if (maxx < a[i]){
maxx = a[i];
maxxIdx = i;
}
}
cout << dfs(maxxIdx).second << "\n";
}
return 0;
}
P7706 「Wdsr-2.7」文文的摄影布置
原题 · 标签:#数据结构 #线段树
考点
线段树,推理节点信息与合并
题意
- 有 \(n\) 张照片排成一列,第 \(i\) 张有吸引度 \(A_i\) 和大小 \(B_i\)。
- 定义 \(\psi(i,k) = A_i + A_k - \min_{i < j < k} B_j\),即选三个位置 \(i < j < k\),用两端吸引度之和减去中间最小的大小。
- \(m\) 次操作,支持三种:
- 单点修改 \(A_x\) 为 \(y\);
- 单点修改 \(B_x\) 为 \(y\);
- 区间查询 \([l, r]\),求 \(\max_{l \le i < i+1 < k \le r} \psi(i,k)\)。
- 保证询问区间长度 \(\ge 3\)。
数据范围:\(1 \le n, m \le 5 \times 10^5\),\(1 \le A_i, B_i, y \le 10^8\)。\(1 \le x \le n\),\(1 \le l \le r \le n\),\(r - l + 1 \ge 3\)。
思路
考虑维护一个区间的maxA,minB,maxAiBj,maxBjAk,ans。
当在叶子节点时,\(maxA = a_i\) 且 \(minB = b_i\)。其余各项为负无穷。
维护maxAiBj和maxBjAk,考虑最大的权值可能出现在左节点,右节点或者左一个右一个。对三种情况做max操作即可。
维护ans,同理,分开左节点,右节点和结合的情况,取max。
pushup可以用引用写,在build,change和query里可以用三次,学到了。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 2e6+10;
int n,m;
struct NODE{
int maxA,minB,maxAiBj,maxBjAk,ans;
}tree[N];
int a[N],b[N];
int ls(int p) {return (p * 2);}
int rs(int p) {return (p * 2 + 1);}
void pushup(NODE &lson,NODE &rson,NODE &res){
res.maxA = max(lson.maxA,rson.maxA);
res.minB = min({lson.minB,rson.minB});
res.maxAiBj = max({lson.maxAiBj,rson.maxAiBj,lson.maxA - rson.minB});
res.maxBjAk = max({lson.maxBjAk,rson.maxBjAk,rson.maxA - lson.minB});
res.ans = max({lson.ans,rson.ans,lson.maxAiBj+rson.maxA,lson.maxA+rson.maxBjAk});
}
void build(int u,int l,int r){
if (l == r){
tree[u].maxA = a[l];
tree[u].minB = b[l];
tree[u].maxAiBj = tree[u].maxBjAk = tree[u].ans = -1 * inf;
return;
}
int mid = (l + r)/ 2;
build(ls(u),l,mid);
build(rs(u),mid+1,r);
pushup(tree[ls(u)],tree[rs(u)],tree[u]);
}
void change(int u,int l,int r,int d){
if (l == r){
tree[u].maxA = a[d];
tree[u].minB = b[d];
return;
}
int mid = (l + r) / 2;
if (d <= mid){
change(ls(u),l,mid,d);
}
if (d > mid){
change(rs(u),mid+1,r,d);
}
pushup(tree[ls(u)],tree[rs(u)],tree[u]);
}
NODE query(int u,int l,int r,int L,int R){
if (L <= l && r <= R){
return tree[u];
}
int mid = (l + r) / 2;
if (L > mid) return query(rs(u),mid+1,r,L,R);
if (R <= mid) return query(ls(u),l,mid,L,R);
NODE res;
NODE left = query(ls(u),l,mid,L,R);
NODE right = query(rs(u),mid+1,r,L,R);
pushup(left,right,res);
return res;
}
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> n >> m;
f(i,1,n){
cin >> a[i];
}
f(i,1,n){
cin >> b[i];
}
build(1,1,n);
f(i,1,m){
int op,x,y;
cin >> op >> x >> y;
if (op == 1){
a[x] = y;
change(1,1,n,x);
}
else if (op == 2){
b[x] = y;
change(1,1,n,x);
}
else{
cout << query(1,1,n,x,y).ans << "\n";
}
}
return 0;
}
P15233 「CROI · R3」浣熊的蒲公英
原题 · 标签:#表达式 #数据结构 #并查集 #离线
考点
操作离线后反向处理
题意
- 给定一个由 \(n\) 个正整数和 \(n-1\) 个运算符(
+、-、*)组成的表达式(无括号),按常规优先级计算。 - 先输出初始表达式的计算结果(对 \(10^9+7\) 取模)。
- 有 \(q\) 次修改操作,每次给出位置 \(k\) 和字符 \(c\):
- 若第 \(k\) 个运算符当前为
*且 \(c\) 不为*,则将其修改为 \(c\),并输出修改后表达式的计算结果(对 \(10^9+7\) 取模); - 否则不操作也不输出。
- 若第 \(k\) 个运算符当前为
- 修改之间不独立,会保留。
数据范围:\(2 \le n \le 10^5\),\(1 \le q \le 10^5\),\(1 \le a_i \le 10^9\)。表达式长度不超过 \(1.1 \times 10^6\)。运算符仅为 +、-、*。
思路
发现*变+很麻烦,那就离线下来反着考虑,+变*。
乘法是连通块,用并查集维护。
反正是一个细节很多的题,还是动用了AI的力量调。表达式的题真恶心。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define f(i,a,b) for(int i=(a);i<=(b);i++)
const int N = 1e5+10;
const int MOD = 1e9+7;
char sym[N];
int nums[N], fa[N], val[N], block_start[N];
string s; int q, cnt;
int find(int u) { return fa[u]==u ? u : fa[u]=find(fa[u]); }
void mergeSimple(int u, int v) {
int fu = find(u), fv = find(v);
if (fu == fv) return;
fa[fu] = fv;
val[fv] = val[fu] * val[fv] % MOD;
block_start[fv] = block_start[fu];
}
// 依托大的
int mergeDelta(int u, int v, char sign_v) {
int fx = find(u), fy = find(v);
if (fx == fy) return 0;
int oldX = val[fx], oldY = val[fy];
int new_val = oldX * oldY % MOD;
int sL = (block_start[fx] == 1) ? 1 : (sym[block_start[fx]] == '-' ? -1 : 1);
int sR = (sign_v == '-' ? -1 : 1);
int t1 = ((sL * new_val) % MOD + MOD) % MOD;
int t2 = ((sL * oldX) % MOD + MOD) % MOD;
int t3 = ((sR * oldY) % MOD + MOD) % MOD;
int delta = (t1 - t2 - t3) % MOD;
delta = (delta + MOD) % MOD;
fa[fx] = fy;
val[fy] = new_val;
block_start[fy] = block_start[fx];
return delta;
}
void init() {
cnt = 1;
for (char c : s) {
if (isdigit(c)) nums[cnt] = nums[cnt] * 10 + c - '0';
else sym[++cnt] = c;
}
f(i, 1, cnt) {
fa[i] = i;
val[i] = nums[i] % MOD;
block_start[i] = i;
}
}
struct OPT { int k; char c; int ans; bool flag; } opt[N];
signed main() {
cin >> s; init();
cin >> q;
f(i, 1, q) {
cin >> opt[i].k >> opt[i].c;
if (sym[opt[i].k + 1] == '*' && opt[i].c != '*') {
sym[opt[i].k + 1] = opt[i].c;
opt[i].flag = 1;
}
}
f(i, 2, cnt) if (sym[i] == '*') mergeSimple(i-1, i);
int cur = val[find(1)];
f(i, 2, cnt) {
if (sym[i] == '+') cur = (cur + val[find(i)]) % MOD;
if (sym[i] == '-') cur = (cur - val[find(i)] + MOD) % MOD;
}
for (int i = q; i >= 1; i--) {
if (!opt[i].flag) continue;
opt[i].ans = cur;
cur = (cur + mergeDelta(opt[i].k, opt[i].k + 1, opt[i].c)) % MOD;
sym[opt[i].k + 1] = '*';
}
cout << cur << "\n";
f(i, 1, q) if (opt[i].flag) cout << opt[i].ans << "\n";
return 0;
}
P13514 KOI 2025 1 干草堆
原题 · 标签:#数据结构 #线段树
考点
离线思想,线段树上二分
题意
- 数轴位置 \(0\) 向右发射力量为 \(P\) 的箭。整数位置 \(i\)(\(1 \le i \le N\))可放置一个防御力为 \(D_i\) 的干草堆。
- 箭撞到干草堆时:若 \(P \le D_i\),箭停止;否则 \(P\) 减去 \(D_i\),箭继续飞行。
- 定义 \(f(X, P)\) 为使力量为 \(P\) 的箭在位置 \(\le X\) 处停止所需安装的干草堆最小数量;若无法停止则为 \(-1\)。
- \(Q\) 次询问,每次给定 \(X, P\),求 \(f(X, P)\)。
数据范围:\(1 \le N, Q \le 3 \times 10^5\),\(1 \le D_i, P_j \le 10^9\),\(1 \le X_j \le N\)。
思路
将操作离线,按照 \(X\) 排序。
建立动态开点权值线段树,从左往右处理加入权值的操作。处理询问就是对于 \(X\) 为当前处理的 \(i\),在线段树上二分,找到最终落在的位置,累加数量。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 3e5+10;
struct NODE{
int sum; // 总和
int cnt; // 个数
int ls,rs;
}tree[N << 4];
int d[N];
struct ASK{
int x,p,idx;
}ask[N];
int ans[N];
bool cmp(ASK a,ASK b){
return a.x < b.x;
}
int n,q;
int cnt;
int newNode(){
++cnt;tree[cnt].ls = tree[cnt].rs = 0;tree[cnt].sum = 0;
return cnt;
}
void pushup(int u){
tree[u].sum = tree[tree[u].ls].sum + tree[tree[u].rs].sum;
tree[u].cnt = tree[tree[u].ls].cnt + tree[tree[u].rs].cnt;
}
int update(int u,int l,int r,int c){
if (!u) u = newNode();
// Dl(u);
if (l == r){
tree[u].sum += c;
tree[u].cnt ++;
return u;
}
int mid = (l + r) /2 ;
if (c <= mid){
tree[u].ls = update(tree[u].ls,l,mid,c);
}
else{
tree[u].rs = update(tree[u].rs,mid+1,r,c);
}
pushup(u);
return u;
}
int bin(int u,int l,int r,int c){ // 返回区间内选中的个数
if (l == r){
return (c / l + (c % l != 0));
}
int mid = (l + r) / 2;
if (tree[tree[u].rs].sum >= c){
return bin(tree[u].rs,mid+1,r,c);
}
else{
return bin(tree[u].ls,l,mid,c-tree[tree[u].rs].sum) + tree[tree[u].rs].cnt;
}
}
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> n >> q;
f(i,1,n) cin >> d[i];
f(i,1,q) cin >> ask[i].x >> ask[i].p,ask[i].idx = i;
sort(ask+1,ask+1+q,cmp);
int sum = 0;
int root = 0;
int idx = 1;
f(i,1,n){
sum += d[i];
root = update(root,1,1e9,d[i]);
while (idx <= q && ask[idx].x <= i){
if (sum < ask[idx].p){
// D(ask[idx].idx);
ans[ask[idx].idx] = -1;
}
else{
// debug
// if (ask[idx].idx == 1){
// D(i);
// D(sum);
// }
ans[ask[idx].idx] = bin(root,1,1e9,ask[idx].p);
}
idx++;
}
}
f(i,1,q){
cout << ans[i] << "\n";
}
return 0;
}
CF707D Persistent Bookcase
原题 · 标签:#数据结构 #离线
考点
离线思想,构造操作树
思路
本题离线做法。
考虑操作实际上是一个树的结构。恢复操作就是将之后的操作链接到指定节点的下面。所以可以构建操作树。
通过在操作树上dfs求出每个节点的答案。
题目要求的操作可以用bitset实现。
本题回溯操作实现时要注意是否真的有操作。
代码
#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 1e3+10;
const int Q = 1e5+10;
struct OPT{
int op,i,j,idx;
}opt[Q];
vector <int> tree[Q];
int ans[Q];
int n,m,q;
bitset<N> b[N];
int del;
bool run(int u,bool isRe){
del = 0;
if (opt[u].op == 1){
bool old = b[opt[u].i][opt[u].j];
b[opt[u].i][opt[u].j] = isRe ? 0 : 1;
del = b[opt[u].i][opt[u].j] - old;
return (old != b[opt[u].i][opt[u].j]);
}
else if (opt[u].op == 2){
bool old = b[opt[u].i][opt[u].j];
b[opt[u].i][opt[u].j] = isRe ? 1 : 0;
del = b[opt[u].i][opt[u].j] - old;
return (old != b[opt[u].i][opt[u].j]);
}
else if (opt[u].op == 3){
b[opt[u].i] ^= b[0];
del = b[opt[u].i].count() + b[opt[u].i].count() - m;
return true;
}
return false;
}
void dfs(int u,int fa){
bool flag = run(u,0);
// int res = 0;
ans[u] = ans[fa] + del;
// D(u);D(ans[u]);Dl(res);
for(int v:tree[u]){
dfs(v,u);
}
if (flag)run(u,1);
}
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> n >> m >> q;
f(i,1,m) b[0][i] = 1;
int last = 0,cnt = 0;
f(idx,1,q){
int op; cin >> op;
if (op == 1){
int i;int j;cin >> i >> j;
tree[last].push_back(++cnt);
opt[cnt] = {op,i,j,idx};
last = cnt;
}
else if (op == 2){
int i;int j;cin >> i >> j;
tree[last].push_back(++cnt);
opt[cnt] = {op,i,j,idx};
last = cnt;
}
else if (op == 3){
int i;cin >> i;
tree[last].push_back(++cnt);
opt[cnt] = {op,i,0,idx};
last = cnt;
}
else{
int k;cin >> k;
last = k;
tree[last].push_back(++cnt);
opt[cnt] = {op,k,0,idx};
last = cnt;
}
}
dfs(0,0);
f(i,1,q){
cout << ans[i] << "\n";
}
return 0;
}
CF1998E1 Eliminating Balls With Merging (Easy Version)
原题 · 标签:#数据结构 #笛卡尔树
考点
笛卡尔树,考察子树和父节点的含义
题意
- 有 \(n\) 个球排成一排,第 \(i\) 个球上写有值 \(a_i\)。
- 对于每个 \(i\),定义 \(f(i)\) 如下:初始集合 \(S = \{1, 2, \ldots, i\}\),每次选择 \(l \in S\)(\(l\) 不是 \(S\) 中最大元素),设 \(r\) 为 \(S\) 中大于 \(l\) 的最小元素:
- 若 \(a_l > a_r\),则 \(a_l = a_l + a_r\),删除 \(r\);
- 若 \(a_l < a_r\),则 \(a_r = a_l + a_r\),删除 \(l\);
- 若 \(a_l = a_r\),可任选删除 \(l\) 或 \(r\),对应更新另一者的值。
- 进行恰好 \(i-1\) 次操作后 \(S\) 只剩一个元素。\(f(i)\) 表示可能的最终剩余元素 \(j\)(\(1 \le j \le i\))的个数。
- 本题为简单版本,\(x=n\),需要求 \(f(n)\)。
数据范围:\(1 \le n \le 2 \times 10^5\),\(1 \le t \le 10^4\),\(1 \le a_i \le 10^9\),所有测试用例 \(\sum n \le 2 \times 10^5\)。
思路
大吞小,考虑建立笛卡尔树。
一个节点被卡的直接原因是:其穷尽全力也无法吞并父节点。
那笛卡尔树就很适合处理这个情况:以当前节点为根的子树的和就是其一定能达到的大小。
如果吞了这些都不能吞掉父节点,那就不可能留到最后。
当前节点吞掉父节点后,就可以吃以父节点为根的子树内的所有节点。状况和将一开始的节点看做父节点一样了。
最后如果吞掉了最大的节点——整个笛卡尔树的根,就说明能留在最后。
也就是说,如果以当前节点为根的子树的和大于等于父节点,就将其与父节点合并。最后求最大节点所在连通块的大小。
本题为简单版本,用两个深搜处理就可以了。 ^e1b837
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
int t;
int n,x;
vector<int> a,ls,rs,sz,vis;
int ans;
void init(){
ans = 0;
a.clear();a.resize(n+1);ls.clear();ls.resize(n+1);rs.clear();rs.resize(n+1);sz.clear();sz.resize(n+1);vis.clear();vis.resize(n+1);
}
void dfs1(int u,int fa){
sz[u] = a[u];
if (ls[u]){
dfs1(ls[u],u);
sz[u] += sz[ls[u]];
}
if (rs[u]){
dfs1(rs[u],u);
sz[u] += sz[rs[u]];
}
if (sz[u] >= a[fa]){
vis[u] = 1;
}
}
void dfs2(int u){
if (vis[u]) ans++;
if (ls[u] && vis[ls[u]]) dfs2(ls[u]);
if (rs[u] && vis[rs[u]]) dfs2(rs[u]);
}
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> t;
while (t--){
cin >> n >> x;
init();
f(i,1,n){
cin >> a[i];
}
if (n == 1){
cout << "1\n";
continue;
}
int maxx = 0,maxxIdx = 0;
stack <int> stk;
f(i,1,n){
int last = 0;
while (!stk.empty() && a[stk.top()] < a[i]){
last = stk.top();
stk.pop();
}
ls[i] = last;
if (!stk.empty()){
rs[stk.top()] = i;
}
stk.push(i);
if (maxx < a[i]){
maxx = a[i];
maxxIdx = i;
}
}
dfs1(maxxIdx,0);
dfs2(maxxIdx);
cout << ans << '\n';
}
return 0;
}
浙公网安备 33010602011771号