先预处理每个节点的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((kn∗logk+K)∗logn)
当 k 取 n log n \sqrt n \log n nlogn 时,复杂度达到最优: O ( n n log n ) O(n \sqrt n \log n) O(nnlogn)
代码:
#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;
}