BZOJ4867 : [Ynoi2017]舌尖上的由乃(dfs序,分块)

在这里插入图片描述


先预处理每个节点的dfs序,问题转变为:区间查询第 k 小,区间修改。

这个问题线段树和主席树都无法解决,考虑用分块暴力。

对序列进行分块,每个块维护一个 pair(dfs序,权值),维护每个块的按权值排序后的数组。

对于区间修改:整块打块标记 O(1)修改,零散块需要重构,因为原块有序,可以使用归并来重构,复杂度 O ( k + n k ) O(k + \frac{n}{k}) O(k+kn),k为块的大小。
对于区间查询:二分答案,问题变成 查询区间内有多少个数字小于等于 二分值 mid,可以在块内二分。复杂度 O ( ( n k ∗ log ⁡ k + K ) ∗ log ⁡ n ) O((\frac{n}{k}*\log k + K)*\log n) O((knlogk+K)logn)

当 k 取 n log ⁡ n \sqrt n \log n n logn 时,复杂度达到最优: O ( n n log ⁡ n ) O(n \sqrt n \log n) O(nn logn)


代码:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
const int maxn = 1e5 + 10;
vector<pii> g[maxn];
int n,m,len,st[maxn],ed[maxn],dep[maxn],cnt,lim = 0;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
inline void prework(int u) {
	st[u] = ++cnt;
	for(auto it : g[u]) {
		dep[it.fir] = dep[u] + it.sec;
		prework(it.fir);
	}
	ed[u] = cnt;
}
struct BlockDiv{
	int block,num,add[maxn];							//块标记,块大小,分块数组 
	pii p[maxn];						
	inline void init() {
		block = 0;
		for(int i = 0; i <= n; i++)
			p[i] = pii(i,0);
		memset(add,0,sizeof add);
	}
	inline void change(int k,int l,int r,int v) {				//归并处理零散块 
		pii t1[1400],t2[1400];
		int p1 = 0,p2 = 0,lp = 1,rp = 1,tot = (k - 1) * block;
		for(int i = (k - 1) * block + 1; i <= min(k * block,n); i++) {		//归并重构左半边的零散块 
			if(p[i].fir < l || p[i].fir > r) t1[++p1] = p[i];
			else t2[++p2] = pii(p[i].fir,p[i].sec + v);
		}
		while(lp <= p1 && rp <= p2) {
			if(t1[lp].sec <= t2[rp].sec) {
				p[++tot] = t1[lp];
				lp++;
			} else {
				p[++tot] = t2[rp];
				rp++;
			}
		}
		while(lp <= p1) p[++tot] = t1[lp],lp++;
		while(rp <= p2) p[++tot] = t2[rp],rp++;
	}
	inline void update(int l,int r,int v) {					//区间更新:加上 v 
		int lp = l / block + (l % block > 0);			//求出端点所在块号 
		int rp = r / block + (r % block > 0);
		for(int i = lp + 1; i < rp; i++)				//中间的整块打标记 
			add[i] += v;
		change(lp,l,r,v);								//重构离散块 
		if(lp < rp) change(rp,l,r,v);
	}
	inline int qry(int l,int r,int k) {
		int lp = l / block + (l % block > 0);			
		int rp = r / block + (r % block > 0);
		int lv = 0,rv = lim;
		while(lv < rv) {
			int mid = lv + rv >> 1,sum = 0;
			for(int i = lp + 1; i < rp; i++) {
				int s = (i - 1) * block + 1,t = i * block + 1;
				while(s < t) {
					int m = s + t >> 1;
					if(p[m].sec + add[i] > mid) t = m;
					else s = m + 1;
				}
				sum += s - ((i - 1) * block + 1);
			}
			for(int i = (lp - 1) * block + 1; i <= lp * block; i++)
				if(p[i].fir >= l && p[i].fir <= r && p[i].sec + add[lp] <= mid) sum++;
			if(lp < rp) {
				for(int i = (rp - 1) * block + 1; i <= min(n,rp * block); i++)
					if(p[i].fir >= l && p[i].fir <= r && p[i].sec + add[rp] <= mid) sum++;
			}
			if(sum >= k) rv = mid;
			else lv = mid + 1;
		}
		return lv;
	}
	
}B;
int main() {
	n = read(); m = read(); len = read();
	for(int i = 2,f,w; i <= n; i++) {
		f = read(); w = read();
		g[f].push_back(pii(i,w));
	}
	prework(1);
	B.init();
	B.block = min(n,1200);
	B.num = n / B.block + (n % B.block > 0);
	for(int i = 1; i <= n; i++) {
		B.update(st[i],st[i],dep[i]);
		lim = max(lim,dep[i]);
	}
	while(m--) {
		int op,x,y;
		op = read(); x = read(); y = read();
		if(op == 1) {
			if(y > ed[x] - st[x] + 1) puts("-1");
			else printf("%d\n",B.qry(st[x],ed[x],y));
		} else {
			B.update(st[x],ed[x],y);
			lim += y;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值