Loading

2026.8 - 做题记录与方法总结

2026.8 - 做题记录与方法总结

上个月打了几局 ABC,CF,发现本人从菜鸟退化成区了(汗

2026.8.3

P3380 【模板】树套树

用树状数组维护前缀线段树,通过两个临时数组来走所需的 \(\log\) 级 线段树,通过查询时遍历所需的线段树来确定答案。

线段树部分:

namespace tree{
int ls[N<<7],rs[N<<7],sum[N<<7],tot;
#define mid ((pl + pr) >> 1)
void push_up(int p) {
	sum[p] = sum[ls[p]] + sum[rs[p]];
}

void edit(int &p,int pl,int pr,int k,int d) {
	if(!p) p = ++tot;
	if(pl == pr) {
		sum[p] += d;
		return;
	}
	if(k <= mid) edit(ls[p],pl,mid,k,d);
	else edit(rs[p],mid+1,pr,k,d);
	push_up(p);
}

int query_num(int pl,int pr,int k) {
	if(pl == pr) return pl;
	int res = 0;
	for(int i = 1;i<=cnt;i++) res += sum[ls[tem[i]]];
	for(int i = 1;i<=num;i++) res -= sum[ls[tmp[i]]];
	if(k <= res) {
		for(int i = 1;i<=cnt;i++) tem[i] = ls[tem[i]];
		for(int i = 1;i<=num;i++) tmp[i] = ls[tmp[i]];
		return query_num(pl,mid,k);
	}else{
		for(int i = 1;i<=cnt;i++) tem[i] = rs[tem[i]];
		for(int i = 1;i<=num;i++) tmp[i] = rs[tmp[i]];
		return query_num(mid+1,pr,k - res);
	}
}

int query_rank(int pl,int pr,int k) {
	if(pl == pr) return 0;
	int res = 0;
	if(k <= mid) {
		for(int i = 1;i<=cnt;i++) tem[i] = ls[tem[i]];
		for(int i = 1;i<=num;i++) tmp[i] = ls[tmp[i]];
		return query_rank(pl,mid,k);
	}else {
		for(int i = 1;i<=cnt;i++) res += sum[ls[tem[i]]],tem[i] = rs[tem[i]];
		for(int i = 1;i<=num;i++) res -= sum[ls[tmp[i]]],tmp[i] = rs[tmp[i]];
		return res + query_rank(mid + 1,pr,k);
	}
}

}

其中 \(cnt,num\) 为两类前缀线段树(\([1,R]\)\([1,L-1]\))的个数,每次进查询都做遍历,相当于在合并树上做操作。

树状数组操作:

void add(int x,int d) {
	for(int i = x;i<=n;i += lowbit(i))
		tree::edit(rt[i],1,len,a[x],d);
}

在树状数组的每个遍历节点上做权值线段树的 \(+1\) 操作。

数据结构外操作:

int find_num(int l,int r,int k) {
	cnt = num = 0;
	for(int i = r;i;i -= lowbit(i))
		tem[++cnt] = rt[i];
	for(int i = l - 1;i;i -= lowbit(i))
		tmp[++num] = rt[i];
	return tree::query_num(1,len,k);
}

int find_rank(int l,int r,int k) {
	cnt = num = 0;
	for(int i = r;i;i -= lowbit(i))
		tem[++cnt] = rt[i];
	for(int i = l - 1;i;i -= lowbit(i))
		tmp[++num] = rt[i];
	return tree::query_rank(1,len,k) + 1;
}

每次清空 \(cnt,num\) 来跑两个前缀线段树(\(query_rank\) 查小于部分个数)

int find_pre(int l,int r,int k) {
	int rnk = find_rank(l,r,k) - 1;
	if(rnk == 0) return 0;
	return find_num(l,r,rnk);
}

int find_suc(int l,int r,int k){
	if(k == len) return len + 1;
	int rnk = find_rank(l,r,k + 1);
	if(rnk == r - l + 2) return len + 1;
	return find_num(l,r,rnk);
}

查前驱和后继部分,小心上下界。

主函数要主要离散化,尽可能减少内存使用!

询问内容也要离散化!

signed main() {
	n = rd(),m = rd();
    for(int i = 1;i<=n;i++) a[i] = rd();
	for(int i = 1;i<=n;i++) hsh[++len] = a[i];
	for(int i = 1;i<=m;i++) {
		q[i][0] = rd();
		q[i][1] = rd();
		q[i][2] = rd();
		if(q[i][0] ^ 3) q[i][3] = rd();
		else hsh[++len] = q[i][2];
		if(q[i][0] == 1 || q[i][0] == 4 || q[i][0] == 5) hsh[++len] = q[i][3];
	}
	sort(hsh + 1,hsh + len + 1);
	len = unique(hsh + 1,hsh + len + 1) - hsh - 1;
	hsh[0] = -inf;hsh[len + 1] = inf;
	for(int i = 1;i<=n;i++){
		a[i] = lower_bound(hsh + 1,hsh + len + 1,a[i]) - hsh;
		add(i,1);
	} 
    for(int i = 1;i<=m;i++){
        int opt = q[i][0];
        switch (opt)
        {
        case 1:
			q[i][3] = lower_bound(hsh + 1,hsh + len + 1,q[i][3]) - hsh;
			wt(find_rank(q[i][1],q[i][2],q[i][3]));putchar('\n');
            break;
        case 2:
			wt(hsh[find_num(q[i][1],q[i][2],q[i][3])]);
			putchar('\n');
            break;
        case 3:
			add(q[i][1],-1);
			a[q[i][1]] = lower_bound(hsh + 1,hsh + len + 1,q[i][2]) - hsh;
			add(q[i][1],1);
            break;
        case 4:
			q[i][3] = lower_bound(hsh + 1,hsh + len + 1,q[i][3]) - hsh;
			wt(hsh[find_pre(q[i][1],q[i][2],q[i][3])]);putchar('\n');
            break;
        case 5:
			q[i][3] = lower_bound(hsh + 1,hsh + len + 1,q[i][3]) - hsh;
			wt(hsh[find_suc(q[i][1],q[i][2],q[i][3])]);putchar('\n');
            break;
        default:
            cerr<<"Error";
            return 0;
            break;
        }
    }

	return 0;
}
posted @ 2026-08-03 16:34  MingJunYi  阅读(1)  评论(0)    收藏  举报