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\) 次操作,支持三种:
    1. 单点修改 \(A_x\) 为 \(y\);
    2. 单点修改 \(B_x\) 为 \(y\);
    3. 区间查询 \([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\) 取模);
    • 否则不操作也不输出。
  • 修改之间不独立,会保留。

数据范围:\(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;
}