1 条题解
-
0
自动搬运
来自洛谷,原作者为

liuzhangfeiabc
**搬运于
2025-08-24 21:57:20,当前版本为作者最后更新于2018-03-04 21:30:34,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
经典的带修树链第k大问题。
先说说几种道听途说的做法:
1、树剖+线段树套平衡树,查询时需要二分答案。
复杂度达到了惊人的4个log,更惊人的是据说妥妥能过。(实在懒得写写试试了qwq)
2、树剖+树状数组套主席树(或者树状数组套trie之类的),这样好处在于:主席树天生就可以直接求第k大而不用像平衡树那样二分答案。
(看似平衡树可以直接求第k大,实际上这样没法合并多棵平衡树的答案,必须得二分,而主席树不用。)
总之就是用树剖花1个log的代价把树上问题转化成序列问题,然后做法就多种多样了。
这样是3个log的,同样能过。
然后就是我写的做法:直接写树状数组套主席树。
前面几种做法都避不开树剖这一环,如果能省去这一步的话就能做到更优的复杂度。
考虑不带修的树链第k大,本质上就是查询4条链:根到u的答案+根到v的答案-根到lca的答案-根到lca父亲的答案。
所以我们可以像序列上那样建出前缀主席树,每个节点的主席树存储的是根到它路径上的所有点,然后同时查询4棵主席树就行了。
然而这样的话修改一个节点的权值会影响到它的子树内所有的节点,于是我们不得不暴力修改O(n)棵主席树。
不过既然一个点的修改只会对子树内的节点产生整体影响,我们可以考虑维护dfs序,因为一棵子树在dfs序上是一段连续的区间。
所以问题就变成了:dfs序上的区间修改,单点查询。这可以用树状数组实现。
具体操作是:把所有的主席树进行差分,差分后每棵主席树每个位置的值相当于原来它这个位置的值-原来它在dfs序上的前一棵主席树这个位置的值,这样可能会产生负数不过我们不用管。
修改时就是把区间修改转化成两个单点修改,查询时一次性查询4条链对应的前缀主席树。
于是这个题变成了两个log。
(不过据说这个题有1个log的神奇解法,不过我不会啊qwq)
上代码:
//ctsc2008 network:树上带修链上第k大 #include<bits/stdc++.h> using namespace std; #define gc getchar() #define pc putchar #define f(i) st[0][i] #define md int mid = l + r >> 1 #define ln t[q].ls,l,mid #define rn t[q].rs,mid + 1,r #define md int mid = l + r >> 1 int r(){ int x = 0; char c; c = gc; while(!isdigit(c)) c = gc; while(isdigit(c)){ x = (x << 1) + (x << 3) + c - '0'; c = gc; } return x; } void p(int q){ if(q >= 10) p(q / 10); pc(q % 10 + '0'); } int n,m; struct edge{ int to,nxt; }e[200010]; int fir[100010],cnt; void ins(int u,int v){ e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt; e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt; } struct qry{ int k,u,v; }b[100010]; struct lsh{ int a,id; bool bh; }l[200010]; int ct,ct2,mx; int dy[200010]; int fsts[100010],nxt[100010],dpt[100010],a[100010],st[20][100010]; int dfsx[100010],wz[100010],nw,sz[100010]; void dfs(int q){//打出dfs序 dfsx[++nw] = q; wz[q] = nw; sz[q] = 1; for(int i = fir[q];i;i = e[i].nxt){ int j = e[i].to; if(f(q) == j) continue; f(j) = q; nxt[j] = fsts[q]; fsts[q] = j; dpt[j] = dpt[q] + 1; dfs(j); sz[q] += sz[j]; } } inline void buildst(){ for(register int i = 1;i <= 17;++i){ for(register int j = 1;j <= n;++j) st[i][j] = st[i - 1][st[i - 1][j]]; } } bool cp(lsh q,lsh w){ return q.a < w.a; } int lca(int u,int v){ if(dpt[u] < dpt[v]) swap(u,v); int z = dpt[u] - dpt[v],i = 0; while(z){ if(z & 1) u = st[i][u]; z >>= 1; ++i; } if(u == v) return u; for(i = 17;i >= 0;--i){ if(st[i][u] == st[i][v]) continue; u = st[i][u]; v = st[i][v]; } return f(u); } int rt[100010]; struct tree{ int a,ls,rs; }t[20000010]; void mdf(int &q,int l,int r,int x,int fx){//对一棵主席树进行修改 if(!q) q = ++ct; t[q].a += fx; if(l == r) return; md; if(x <= mid) mdf(ln,x,fx); else mdf(rn,x,fx); } void xg(int q,int x,int fx){//一个单点修改操作 for(int i = q;i <= n;i += (i & -i)) mdf(rt[i],1,mx,x,fx); } int now[100010]; int ts[100010],ft; bool vst[100010]; int cx(int u,int v,int w,int p,int x){//查询操作,注意4条链的贡献是u + v - w - p int i; ft = 0; for(i = u;i;i -= (i & -i)) { if(vst[i]) continue; ts[++ft] = i; vst[i] = 1; } for(i = v;i;i -= (i & -i)) { if(vst[i]) continue; ts[++ft] = i; vst[i] = 1; } for(i = w;i;i -= (i & -i)) { if(vst[i]) continue; ts[++ft] = i; vst[i] = 1; } for(i = p;i;i -= (i & -i)) { if(vst[i]) continue; ts[++ft] = i; vst[i] = 1; }//提前存一下4条链会经过的树状数组节点的集合便于处理 for(i = 1;i <= ft;++i) now[ts[i]] = rt[ts[i]]; int l = 1,r = mx,mid,as; while(l < r){ mid = l + r >> 1; as = 0; for(i = u;i;i -= (i & -i)) as += t[t[now[i]].ls].a; for(i = v;i;i -= (i & -i)) as += t[t[now[i]].ls].a; for(i = w;i;i -= (i & -i)) as -= t[t[now[i]].ls].a; for(i = p;i;i -= (i & -i)) as -= t[t[now[i]].ls].a; if(as >= x){ for(i = 1;i <= ft;++i) now[ts[i]] = t[now[ts[i]]].ls; r = mid; } else{ x -= as; for(i = 1;i <= ft;++i) now[ts[i]] = t[now[ts[i]]].rs; l = mid + 1; } } for(i = 1;i <= ft;++i) vst[ts[i]] = 0; return l; } void init(){//初始化 for(int i = 1;i <= n;++i){ xg(wz[i],a[i],1); xg(wz[i] + sz[i],a[i],-1); } } int main(){ int k,u,v,i,w; n = r(); m = r(); for(i = 1;i <= n;++i) { a[i] = r(); l[++ct2].a = a[i]; l[ct2]. bh = 0; l[ct2].id = i; } for(i = 1;i < n;++i){ u = r(); v = r(); ins(u,v); } //以下是离散化部分,不过不离散化应该也行 for(i = 1;i <= m;++i){ b[i].k = r(); b[i].u = r(); b[i].v = r(); if(b[i].k) continue; l[++ct2].a = b[i].v; l[ct2].bh = 1; l[ct2].id = i; } sort(l,l + ct2 + 1,cp); l[0].a = -1; for(i = 1;i <= ct2;++i){ if(l[i].a != l[i - 1].a) ++mx; if(!l[i].bh) a[l[i].id] = mx; else b[l[i].id].v = mx; dy[mx] = l[i].a; } dfs(1); buildst(); init(); for(i = 1;i <= m;++i){ u = b[i].u,v = b[i].v,k = b[i].k; if(!k){ xg(wz[u],a[u],-1); xg(wz[u] + sz[u],a[u],1); a[u] = v; xg(wz[u],a[u],1); xg(wz[u] + sz[u],a[u],-1); } else{ w = lca(u,v); k = dpt[u] + dpt[v] - 2 * dpt[w] - k + 2;//这里把第k大变成了第k小 if(k <= 0){ printf("invalid request!\n"); continue; } p(dy[cx(wz[u],wz[v],wz[w],wz[f(w)],k)]); putchar('\n'); } } return 0; }
- 1
信息
- ID
- 3138
- 时间
- 2000ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者