LG P7447 [Ynoi2007] rgxsxrs Solution

Description

给定序列 a=(a1,a2,⋯ ,an)a=(a_1,a_2,\cdots,a_n)a=(a1,a2,,an),有 mmm 个操作分两种:

  • subtract⁡(l,r,x)\operatorname{subtract}(l,r,x)subtract(l,r,x):对每个 i∈[l,r]i\in[l,r]i[l,r] 执行 ai←ai−x×[ai>x]a_i\gets a_i-x\times [a_i>x]aiaix×[ai>x].
  • query⁡(l,r)\operatorname{query}(l,r)query(l,r):求 ∑i=lrai,min⁡i=lrai,max⁡i=lrai\sum\limits_{i=l}^r a_i,\min\limits_{i=l}^r a_i,\max\limits_{i=l}^r a_ii=lrai,i=lminrai,i=lmaxrai.

Limitations

1≤n,m≤5×1051\le n,m\le 5\times 10^51n,m5×105
1≤ai,x≤1091\le a_i,x\le 10^91ai,x109
1≤l≤r≤n1\le l\le r\le n1lrn
6s,64MB6\text{s},\textcolor{red}{64\text{MB}}6s,64MB

Solution

首先考虑一种势能线段树:

每个节点维护所管辖区间的最值,在执行 subtract⁡\operatorname{subtract}subtract 时:

  • 若当前 max⁡<x\max< xmax<x,操作没有用,直接退出.
  • 若当前 min⁡>x\min> xmin>x,所有数都会被改,给当前节点打上一个减去 xxx 的标记.

但是这玩意的复杂度是错的,只要给一个 1,1091,10^91,109 交替的序列,每次 subtract⁡(1,n,1)\operatorname{subtract}(1,n,1)subtract(1,n,1) 就被卡成 O(nm)O(nm)O(nm) 了.

序列分块似乎也做不了,只能对值域分块,由于 VVV 可达 10910^9109,所以要用倍增分块,将 [bi,bi+1)[b^i,b^{i+1})[bi,bi+1) 分为一块,总共有 ⌈log⁡bV⌉\lceil \log_b V\rceillogbV 块,其中 bbb 为底数.

对每个块维护一棵上面所述的线段树,容易发现每个数最多减 bbb 次就会掉出该块管辖范围,至于这些掉出的元素,因为一个数最多掉出 O(log⁡bV)O(\log_b V)O(logbV) 次,可以直接暴力找块插入.

这样做的时间复杂度为 O(nblog⁡nlog⁡bV)O(nb\log n\log_b V)O(nblognlogbV),空间复杂度为 O(nlog⁡bV)O(n \log_b V)O(nlogbV).
然而由于常数比较大,bbb222 是不够的,需要调大.

空间限制很紧,但是强制在线无法逐块处理,注意到线段树的底下几层很浪费空间,于是若节点的长度小于一个阈值 LLL 时,就直接暴力处理.

bbbLLL 需要根据自己的代码情况调,笔者取的是 b=32,L=32b=32,L=32b=32,L=32.

还有其他细节需要注意:

  • 线段树节点不要存 l,rl,rl,r,容易被卡.
  • 底层暴力时处理插入时,不要给新插入的元素错误地打上标记.
  • 区间的元素不再是 r−l+1r-l+1rl+1 个,需要维护 cnt\textit{cnt}cnt 表示元素个数.
  • 由于所有线段树底层共用一个 aaa,需要先判断是否在本块范围内再操作.

Code

6.83KB,4.37s,12.50MB  (in total, C++20 with O2)6.83\text{KB},4.37\text{s},12.50\text{MB}\;\texttt{(in total, C++20 with O2)}6.83KB,4.37s,12.50MB(in total, C++20 with O2)
只给主要部分.

namespace block {
	struct data {
		i64 sum;
		int max, min, cnt;
		
		inline data() : sum(0), max(0), min(inf), cnt(0) {}
		inline data(i64 _sum, int _max, int _min, int _cnt)
		    : sum(_sum), max(_max), min(_min), cnt(_cnt) {}
	};
	
	inline data operator+(const data& a, const data& b) {
		return data(
		    a.sum + b.sum,
		    std::max(a.max, b.max),
		    std::min(a.min, b.min),
		    a.cnt + b.cnt
		);
	}
	
	inline int ls(int u) { return 2 * u + 1; }
	inline int rs(int u) { return 2 * u + 2; }
	
	struct segment_tree {
		int n, bl, br;
		vector<data> info;
		vector<int> tag;
		
		vector<int>::iterator a;
		vector<bool>::iterator fall;
		
		inline segment_tree() {}
		inline void init(int _n, int _bl, int _br) {
			n = _n, bl = _bl, br = _br;
		    const int cap = std::max(1, n >> 3);
		    info.resize(cap), tag.resize(cap);
		}
		
		inline void leaf_remove(int u, int l, int r, int ql, int qr, int v, vector<int>& trash) {
			data res;
			for (int i = l; i <= r; i++)
			    if (bl <= a[i] && a[i] <= br) {
			        a[i] -= tag[u];
			        if (ql <= i && i <= qr && a[i] > v) {
			            a[i] -= v;
			            if (a[i] < bl) {
			                trash.push_back(i);
			                fall[i] = true;
			                continue;
			            }
			        }
			        res = res + data(a[i], a[i], a[i], 1);
			    }
			
			info[u] = res;
			tag[u] = 0;
		}
		
		inline void leaf_insert(int u, int l, int r) {
			data res;
			for (int i = l; i <= r; i++)
			    if (bl <= a[i] && a[i] <= br) {
			        if (!fall[i]) a[i] -= tag[u];
			        else fall[i] = false;
			        res = res + data(a[i], a[i], a[i], 1);
			    }
			
			info[u] = res;
			tag[u] = 0;
		}
		
		inline data leaf_query(int u, int l, int r, int ql, int qr) {
			data res;
			for (int i = l; i <= r; i++)
			    if (bl <= a[i] && a[i] <= br) {
			        a[i] -= tag[u];
			        if (ql <= i && i <= qr)
			            res = res + data(a[i], a[i], a[i], 1);
			    }
			
			tag[u] = 0;
			return res;
		}
		
		
		inline void pushup(int u) {
			info[u] = info[ls(u)] + info[rs(u)];
		}
	    
	    inline void apply(int u, int k) {
	    	tag[u] += k;
	    	info[u].sum -= 1LL * info[u].cnt * k;
	    	info[u].max -= k, info[u].min -= k;
	    }
	    
	    inline void pushdown(int u) {
	    	if (tag[u]) {
	    		apply(ls(u), tag[u]);
	    		apply(rs(u), tag[u]);
	    		tag[u] = 0;
	    	}
	    }
	    
	    void build(int u, int l, int r) {
		    if (r - l < L) return void(info[u] = leaf_query(u, l, r, l, r));
		    const int mid = (l + r) >> 1;
		    build(ls(u), l, mid);
		    build(rs(u), mid + 1, r);
		    pushup(u);
	    }
	    
	    void insert(int u, int l, int r, int i) {
		    if (r - l < L) return leaf_insert(u, l, r);
		    const int mid = (l + r) >> 1;
		    pushdown(u);
		    if (i <= mid) insert(ls(u), l, mid, i);
		    else insert(rs(u), mid + 1, r, i);
		    pushup(u);
	    }
	    
	    void modify(int u, int l, int r, int ql, int qr, int v, vector<int>& trash) {
	    	if (info[u].max <= v) return;
			if (ql <= l && r <= qr && info[u].min - v >= bl) return apply(u, v);
			if (r - l < L) return leaf_remove(u, l, r, ql, qr, v, trash);
			
			const int mid = (l + r) >> 1;
			pushdown(u);
			if (ql <= mid) modify(ls(u), l, mid, ql, qr, v, trash);
			if (qr > mid) modify(rs(u), mid + 1, r, ql, qr, v, trash);
			pushup(u);
	    }
	    
	    data query(int u, int l, int r, int ql, int qr) {
	    	if (ql <= l && r <= qr) return info[u];
			if (r - l < L) return leaf_query(u, l, r, ql, qr);
			
			const int mid = (l + r) >> 1;
			pushdown(u);
			if (qr <= mid) return query(ls(u), l, mid, ql, qr);
			else if (ql > mid) return query(rs(u), mid + 1, r, ql, qr);
			else return query(ls(u), l, mid, ql, qr) + query(rs(u), mid + 1, r, ql, qr);
	    }
	    
	    inline void insert(int i) {
	    	insert(0, 0, n - 1, i);
	    }
	    
	    inline void subtract(int l, int r, int v, vector<int>& trash) {
	    	modify(0, 0, n - 1, l, r, v, trash);
	    }
	    
	    inline data query(int l, int r) {
	    	return query(0, 0, n - 1, l, r);
	    }
	    
	    inline void build() {
	    	build(0, 0, n - 1);
	    }
	};
	
	struct blocks {
		int n;
		vector<segment_tree> sgt;
		vector<int> trash;
		vector<bool> fall;
		vector<int>::iterator a;
		
		inline blocks() {}
		inline blocks(int _n) : n(_n) {}
		
		inline void init(vector<int>& a) {
			sgt.resize(B + 1);
			fall.resize(n);
			for (int j = 0; j <= B; j++) {
				const int bl = 1 << (j * B);
				const int br = (1 << ((j + 1) * B)) - 1;
				sgt[j].init(n, bl, br);
				sgt[j].a = a.begin();
				sgt[j].fall = fall.begin();
				sgt[j].build();
			}
			
			this->a = a.begin();
		}
		
		inline void subtract(int l, int r, int x) {
			for (int j = 0; j <= B; j++) {
				sgt[j].subtract(l, r, x, trash);
				if (j > 0) {
					for (auto i : trash) 
					    for (int k = 0; k < j; k++)
					        if (sgt[k].bl <= a[i] && a[i] <= sgt[k].br) {
					        	sgt[k].insert(i);
					        	break;
					        }
					trash.clear();
				}
			}
		}
		
		inline data query(int l, int r) {
			data res;
			for (int j = 0; j <= B; j++) res = res + sgt[j].query(l, r);
			return res;
		}
	};
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值