LG P3712 少女与战车 Solution

Description

给定一棵 n n n 个点的树 T T T,边有边权 w i w_i wi,树根为 1 1 1,设 dep ⁡ ( u ) \operatorname{dep}(u) dep(u) u u u 的带权深度,初始时根节点的深度为 0 0 0
执行 q q q 次操作分两种:

  • 1 u k:求 u u u 的子树中第 k k k 小的 dep ⁡ ( u ) \operatorname{dep}(u) dep(u),若不足 k k k 个点输出 − 1 -1 1
  • 2 u x:将 u u u 和它父亲之间边的权值加上 x x x,若 u = 1 u=1 u=1 则把根节点深度加 x x x

保证 w i , x w_i,x wi,x 不大于一个给定常数 L L L

Limitations

1 ≤ n , q ≤ 10 5 1\le n,q\le 10^5 1n,q105
1 ≤ L ≤ 10 1\le L\le 10 1L10
1 ≤ u ≤ n 1\le u\le n 1un
3 s , 500 MB 3\text{s},500\text{MB} 3s,500MB

Solution

L L L 无关的解法。视值域 V V V n n n 同阶。
首先算出每个点的 dep ⁡ \operatorname{dep} dep 并拍到 dfs 序上,那么问题就变为序列 a a a 上区间加,区间第 k k k 小。

考虑使用 P5356 O ( n n log ⁡ 2 n ) O(n\sqrt n \log^ 2n) O(nn log2n) 做法:

对每个查询二分答案,问题变为区间加 x x x,求区间 ≤ x \le x x 的数个数。
考虑分块,对每块维护一个块内排序后的数组 p i p_i pi
查询时,整块在 p i p_i pi 上二分,散块直接暴力。
修改时,整块打标记,散块的 p i p_i pi 分成两部分归并。

然而这样实测过不去,需要如下优化:

  • 二分答案范围没必要那么大,可以取 a l ∼ a r a_l\sim a_r alar 的最值作为边界。
  • 每次 check 都要暴力算散块太慢了,可以在二分答案前,先取两边散块内的元素归并成有序序列 t t tcheck 时散块和整块一样在 t t t 上二分。
  • 经分析得取 B = n log ⁡ n B=\sqrt n\log n B=n logn 最快。

然后加个快读快写就过了。

Code

6.67 KB , 14.38 s , 14.53 MB    (C++20   with   O2) 6.67\text{KB},14.38\text{s},14.53\text{MB}\;\texttt{(C++20 with O2)} 6.67KB,14.38s,14.53MB(C++20 with O2)
只放一部分.

constexpr int cap = 4e7 + 10;
int pool[cap], *ptr = pool;

template<class T>
inline T* alloc(int n) {
	int siz = sizeof(T) / sizeof(int) * n;
	T* res = (T*)ptr; ptr += siz;
	return res;
}


constexpr int inf = 1.5e9;
struct Element {
	int val, id;
	inline Element() {}
	inline Element(int _val, int _id) : val(_val), id(_id) {}
	inline bool operator<(const Element& rhs) const { return val < rhs.val; }
};

struct Block {
	int n, B, blocks;
	int *a, *tag, *bel, *L, *R;
	Element *sorted, *left, *right, *tmp;
	
	inline Block() {}
	inline Block(int _n, int *_a) : a(_a) {
		n = _n;
		B = sqrt(n) * log2(n) + 1;
		blocks = (n + B - 1) / B;
		sorted = alloc<Element>(n), bel = alloc<int>(n);
		left = alloc<Element>(B), right = alloc<Element>(B), tmp = alloc<Element>(2 * B);
		L = alloc<int>(blocks), R = alloc<int>(blocks), tag = alloc<int>(blocks);
		for (int i = 0; i < blocks; i++) {
			L[i] = i * B;
			R[i] = min(L[i] + B, n) - 1;
			for (int j = L[i]; j <= R[i]; j++) {
				bel[j] = i;
				sorted[j] = Element(a[j], j);
			}
			sort(sorted + L[i], sorted + R[i] + 1);
		}
	}
	
	inline void brute_bound(int b, int l, int r, int& vl, int& vr) {
		for (int i = l; i <= r; i++) {
			chmin(vl, a[i] + tag[b]);
			chmax(vr, a[i] + tag[b]);
		}
	}

	inline pair<int, int> bound(int l, int r) {
		int bl = bel[l], br = bel[r];
		int vl = inf, vr = -inf;
		if (bl == br) brute_bound(bl, l, r, vl, vr);
		else {
			brute_bound(bl, l, R[bl], vl, vr);
			brute_bound(br, L[br], r, vl, vr);
			for (int i = bl + 1; i < br; i++) {
				chmin(vl, sorted[L[i]].val + tag[i]);
				chmax(vr, sorted[R[i]].val + tag[i]);
			}
		}
		return make_pair(vl, vr);
	}

	inline int single_merge(int b, int l, int r) {
		int siz = 0;
		for (int i = L[b]; i <= R[b]; i++)
			if (sorted[i].id >= l && sorted[i].id <= r) {
				tmp[siz++] = Element(sorted[i].val + tag[b], sorted[i].id);
			}

		return siz;
	}
	
	inline int partial_merge(int bl, int br, int l1, int r1, int l2, int r2) {
		int lsiz = 0, rsiz = 0;

		for (int i = L[bl]; i <= R[bl]; i++)
			if (sorted[i].id >= l1 && sorted[i].id <= r1) {
				left[lsiz++] = Element(sorted[i].val + tag[bl], sorted[i].id);
			}
		
		for (int i = L[br]; i <= R[br]; i++)
			if (sorted[i].id >= l2 && sorted[i].id <= r2) {
				right[rsiz++] = Element(sorted[i].val + tag[br], sorted[i].id);
			}

		merge(left, left + lsiz, right, right + rsiz, tmp);
		return lsiz + rsiz;
	}
	
	inline int count(int bl, int br, int x, int siz) {
	    int res = upper_bound(tmp, tmp + siz, Element{x, 0}) - tmp;
	    for (int i = bl; i <= br; i++) {
	        res += upper_bound(
	            sorted + L[i], sorted + R[i] + 1, 
	            Element{x - tag[i], 0}
	        ) - sorted - L[i];
	    }
	    
	    return res;
	}
	
	inline int kth(int l, int r, int k) {
		const int len = r - l + 1;
		if (k < 1 || k > len) return -1;
		
		int siz = 0, bl = bel[l], br = bel[r];
		if (bl == br) siz = single_merge(bl, l, r);
		else siz = partial_merge(bl, br, l, R[bl], L[br], r);
		
		auto [vl, vr] = bound(l, r);
		
		if (k == 1) return vl;
		if (k == len) return vr;
		int res = -1;
		while (vl <= vr) {
			int mid = (vl + vr) >> 1;
			if (count(bl + 1, br - 1, mid, siz) < k) vl = mid + 1;
			else vr = (res = mid) - 1;
		}
		return res;
	}
	
	inline void brute_add(int b, int l, int r, int k) {
		int lsiz = 0, rsiz = 0;
		for (int i = L[b]; i <= R[b]; i++) {
			if (i >= l && i <= r) a[i] += k;
			if (sorted[i].id >= l && sorted[i].id <= r)
			    left[lsiz++] = Element(sorted[i].val + k, sorted[i].id);
			else right[rsiz++] = sorted[i];
		}
		std::merge(left, left + lsiz, right, right + rsiz, sorted + L[b]);
	}
	
	inline void add(int l, int r, int k) {
		const int bl = bel[l], br = bel[r];
		if (bl == br) return brute_add(bl, l, r, k);
		brute_add(bl, l, R[bl], k);
		brute_add(br, L[br], r, k);
		for (int i = bl + 1; i < br; i++) tag[i] += k;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值