[BZOJ 3083] 遥远的国度

Link:https://www.lydsy.com/JudgeOnline/problem.php?id=3083

Solution:

算是一道树剖的模板题吧

除了模板以外,新增的操作就是“换$root$”

此时我们分类讨论即可:

1、$x={root}$   查询$[1,n]$   (需要特判

2、$x\not={lca(x,root)}$   直接查询子树即可,不受$root$变化的影响

3、$x={lca(x,root)}$   发现现在x的子树就是整棵树减去$x$往$root$方向向下最近的那个节点的子树,查询其补集即可

dfs序:

也是套路了,由于子树中dfs序连续,用dfs序整体维护子树($root$为$x$的子树范围为$[L[x],R[x]]$)

若$x$往$root$方向最近的点为$v$,则除去其子树的补集为$[1,L[v]-1]+[R[v]+1,n]$

Code:

//by NewErA
#include <bits/stdc++.h>

using namespace std;
const int INF=2147483647;

#define mid (l+r)/2
#define lc k<<1,l,mid
#define rc k<<1|1,mid+1,r

const int MAXN=1e5+5;
int m,n,root=0,siz[MAXN],dep[MAXN],top[MAXN],f[MAXN][20],pos[MAXN],dat[MAXN],L[MAXN],R[MAXN],cnt=0;
int tag[5*MAXN],mn[5*MAXN],w[MAXN];
vector<int> a[MAXN]; void dfs1(int k) { siz[k]=1; for(int i=1;(1<<i)<=dep[k];i++) //最好将f在dfs1就更新掉,不能在处理完叶子结点后再更新 f[k][i]=f[f[k][i-1]][i-1]; for(int i=0;i<a[k].size();i++) { if(a[k][i]==f[k][0]) continue; f[a[k][i]][0]=k; dep[a[k][i]]=dep[k]+1; dfs1(a[k][i]); siz[k]+=siz[a[k][i]]; } } void dfs2(int k,int up) { int bs=0; pos[k]=L[k]=++cnt,top[k]=up; for(int i=0;i<a[k].size();i++) if(a[k][i]!=f[k][0] && siz[a[k][i]]>siz[bs]) bs=a[k][i]; if(!bs){R[k]=L[k];return;} dfs2(bs,up); for(int i=0;i<a[k].size();i++) if(a[k][i]!=f[k][0] && a[k][i]!=bs) dfs2(a[k][i],a[k][i]); R[k]=cnt; } void push_up(int k){mn[k]=min(mn[k<<1],mn[k<<1|1]);} void push_down(int k) { if(tag[k]) { tag[k<<1]=tag[k<<1|1]=tag[k]; mn[k<<1]=mn[k<<1|1]=tag[k]; tag[k]=0; } } void build(int k,int l,int r) { if(l==r){mn[k]=w[l];return;} build(lc);build(rc); push_up(k); } void update_same(int a,int b,int x,int k,int l,int r) { if(a<=l && r<=b) { mn[k]=tag[k]=x; return; } push_down(k); if(a<=mid) update_same(a,b,x,lc); if(b>mid) update_same(a,b,x,rc); push_up(k); } int query_min(int a,int b,int k,int l,int r) { if(a<=l && r<=b) return mn[k]; push_down(k); int ret=INF; if(a<=mid) ret=min(ret,query_min(a,b,lc)); if(b>mid) ret=min(ret,query_min(a,b,rc)); return ret; } void solve_same(int u,int v,int x) { while (top[u]!=top[v]) //这里是while…… { if(dep[top[u]]<dep[top[v]]) swap(u,v); update_same(pos[top[u]],pos[u],x,1,1,n); u=f[top[u]][0]; } if(pos[u]>pos[v]) swap(u,v); update_same(pos[u],pos[v],x,1,1,n); } int LCA(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]<dep[top[v]]) swap(u,v); u=f[top[u]][0]; } if(dep[u]>dep[v]) swap(u,v); return u; } int find_nearest(int u,int depk) //寻找x到root方向最近的节点 { int temp=dep[u]-depk; for(int i=0;i<=17;i++) if(temp&(1<<i)) u=f[u][i]; return u; } int solve_max(int k) { if(k==root) return query_min(1,n,1,1,n); //特判 else { int lca=LCA(k,root); if(lca!=k) return query_min(L[k],R[k],1,1,n); //直接查询子树 else { int nst=find_nearest(root,dep[k]+1); return min(query_min(1,L[nst]-1,1,1,n),query_min(R[nst]+1,n,1,1,n)); } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { int x,y;scanf("%d%d",&x,&y); a[x].push_back(y);a[y].push_back(x); } for(int i=1;i<=n;i++) scanf("%d",&dat[i]); scanf("%d",&root); dfs1(root);dfs2(root,root); for(int i=1;i<=n;i++) w[pos[i]]=dat[i]; build(1,1,n); for(int i=1;i<=m;i++) { int op,x,y,z;scanf("%d%d",&op,&x); if(op==1) root=x; else if(op==2) scanf("%d%d",&y,&z),solve_same(x,y,z); else printf("%d\n",solve_max(x)); } return 0; }

Review:

1、整体维护子树,想到利用dfs序

2、犯的丝帛错误:

(1)将更新f数组的循环放到dfs2中处理完叶子结点后了,最好在dfs1中就更新掉

(2)线段树处理中将最外层的while打成if了……

猜你喜欢

转载自www.cnblogs.com/newera/p/9108295.html