5.树上问题
树上问题
开题顺序: \(ACHKG\)
\(A\) CF600E Lomsat gelral
\(B\) CF708C Centroids
\(C\) CF1706E Qpwoeirut and Vertices
\(D\) luogu P2491 [SDOI2011] 消防
-
选择直径上的边一定不劣。
-
因为边权没有负数,所以用两遍 \(DFS\) 求出任意一条直径。设其上面的节点分别为 \(u_{1 \sim t}\) 。
-
设 \(f_{i}\) 表示从 \(i\) 出发,不经过所选出的直径上的点能到达的距离最远的点的距离。
-
此时 \(u_{i},u_{j}(1 \le i \le j \le t)\) 对答案的贡献为 \(\max\limits(\max\limits_{k=i}^{j}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}})\) 。最终的式子显然是 \(\min\limits_{1 \le i \le j \le t} \{ \max\limits(\max\limits_{k=i}^{j}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}}) \}\) 。
-
而在 \(i\) 固定时, \(j\) 越靠近 \(t\) 越优,双指针维护即可。
-
\(\max\limits_{k=i}^{j}\{ f_{u_{k}} \}\) 单调队列 \(O(n)\) 或者 \(ST\) 表 \(O(n \log n)\) 维护暂且不提,考虑关于 \(\max\limits_{k=i}^{j}\{ f_{u_{k}} \}\) 的一些其他性质。
-
因为都是直径上的端点,容易有 \(f_{u_{k}} \le dis_{u_{1},u_{k}}\) 且 \(f_{u_{k}} \le dis_{u_{t},u_{k}}\) 。
-
而 \(\begin{cases} \forall k \in [1,i),f_{u_{k}} \le dis_{u_{1},u_{k}} \le dis_{u_{1},u_{i}} \\ \forall k \in (j,n],f_{u_{k}} \le dis_{u_{t},u_{k}} \le dis_{u_{t},u_{j}} \end{cases}\) ,故最终结果可以改写为 \(\min\limits_{1 \le i \le j \le t} \{ \max\limits(\max\limits_{k=1}^{t}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}}) \}\) 。
点击查看代码
struct node { int nxt,to,w; }e[600010]; int head[600010],dis[600010],fa[600010],c[600010],vis[600010],sum[600010],f[600010],d[600010],cnt=0; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(int x,int father,int w) { c[x]=w; fa[x]=father; dis[x]=dis[father]+w; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x,e[i].w); } } } void dfs2(int x) { vis[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { dfs2(e[i].to); f[x]=max(f[x],f[e[i].to]+e[i].w); } } } int main() { int n,s,u,v,w,rt1=0,rt2=0,ans=0x7f7f7f7f,maxx=0,i,j; cin>>n>>s; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs1(1,0,0); for(i=1;i<=n;i++) { rt1=(dis[i]>dis[rt1])?i:rt1; } dfs1(rt1,0,0); for(i=1;i<=n;i++) { rt2=(dis[i]>dis[rt2])?i:rt2; } while(rt2!=0) { vis[rt2]=1; d[0]++; d[d[0]]=rt2; rt2=fa[rt2]; } reverse(d+1,d+1+d[0]); for(i=1;i<=d[0];i++) { dfs2(d[i]); maxx=max(maxx,f[d[i]]); sum[i]=sum[i-1]+c[d[i]]; } for(i=j=1;i<=d[0];i++) { while(j+1<=d[0]&&sum[j+1]-sum[i]<=s) { j++; } ans=min(ans,max(maxx,max(sum[i],sum[d[0]]-sum[j]))); } cout<<ans<<endl; return 0; }
\(E\) luogu P4253 [SCOI2015] 小凸玩密室
\(F\) luogu P8990 [北大集训 2021] 小明的树
\(G\) BZOJ3786 星系探索
-
貌似是伪 \(ETT\) 板子。
-
考虑维护整棵树的欧拉序列,设点 \(x\) 的进栈序为 \(in_{x}\) ,出栈序为 \(out_{x}\) ,进栈时的贡献为 \(w_{x}\) ,出栈时的贡献为 \(-w_{x}\) 。
-
操作 \(1\) 的链求和就转化成了 \([1,l_{x}]\) 的区间求和,且容易扩展至树上任意两点间求和。
-
操作 \(2\) 的子树求和直接对着区间打懒惰标记。
-
操作 \(3\) 的换父亲可以转化为 \([in_{x},out_{x}]\) 接到新父亲的入栈序 \(in_{fa}\) 后面,平衡树维护序列即可。
-
在使用 \(FHQ-Treap\) 时因为存在换父亲操作,原序列中的顺序可能发生了改变,需要先查出其 \(rank\) 再进行分裂。具体地,从 \(x\) 往上跳父亲,沿途加上左子树的大小加 \(1\) 。
点击查看代码
struct node { ll nxt,to; }e[200010]; ll head[200010],w[200010],in[200010],out[200010],cnt=0,tot=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct BST { ll rt_sum,root; struct FHQ_Treap { ll son[2],fa,val,rnd,siz,dir,len,sum,lazy; }tree[200010]; #define lson(rt) (tree[rt].son[0]) #define rson(rt) (tree[rt].son[1]) #define fa(rt) (tree[rt].fa) BST() { rt_sum=root=0; srand(time(0)); } void pushup(ll rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+1; tree[rt].len=tree[lson(rt)].len+tree[rson(rt)].len+tree[rt].dir; tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum+tree[rt].val; if(lson(rt)!=0) { fa(lson(rt))=rt; } if(rson(rt)!=0) { fa(rson(rt))=rt; } } ll build_rt(ll val,ll dir) { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].lazy=0; tree[rt_sum].val=tree[rt_sum].sum=val; tree[rt_sum].len=tree[rt_sum].dir=dir; tree[rt_sum].rnd=rand(); tree[rt_sum].siz=1; return rt_sum; } void pushlazy(ll rt,ll lazy) { tree[rt].lazy+=lazy; tree[rt].val+=tree[rt].dir*lazy; tree[rt].sum+=tree[rt].len*lazy; } void pushdown(ll rt) { if(rt!=0&&tree[rt].lazy!=0) { pushlazy(lson(rt),tree[rt].lazy); pushlazy(rson(rt),tree[rt].lazy); tree[rt].lazy=0; } } void split(ll rt,ll k,ll &ls,ll &rs) { if(rt==0) { ls=rs=0; return; } pushdown(rt); if(tree[lson(rt)].siz+1<=k) { ls=rt; split(rson(ls),k-tree[lson(rt)].siz-1,rson(ls),rs); } else { rs=rt; split(lson(rs),k,ls,lson(rs)); } pushup(rt); } ll merge(ll rt1,ll rt2) { if(rt1==0||rt2==0) { return rt1+rt2; } pushdown(rt1); pushdown(rt2); if(tree[rt1].rnd<tree[rt2].rnd) { rson(rt1)=merge(rson(rt1),rt2); pushup(rt1); return rt1; } else { lson(rt2)=merge(rt1,lson(rt2)); pushup(rt2); return rt2; } } void insert(ll val,ll dir) { root=merge(root,build_rt(val,dir)); } ll query_rk(ll rt) { ll ans=tree[lson(rt)].siz+1; for(;fa(rt)!=0;rt=fa(rt)) { ans+=(rson(fa(rt))==rt)*(tree[lson(fa(rt))].siz+1); } return ans; } void change_fa(ll x,ll y) { ll ls,rs,mid; split(root,query_rk(out[x]),ls,rs); fa(ls)=fa(rs)=0;//记得更改父亲 split(ls,query_rk(in[x])-1,ls,mid); fa(ls)=fa(mid)=0; root=merge(ls,rs); fa(root)=0; split(root,query_rk(in[y]),ls,rs); root=merge(merge(ls,mid),rs); fa(root)=0; } void update(ll x,ll val) { ll ls,rs,mid; split(root,query_rk(out[x]),ls,rs); fa(ls)=fa(rs)=0; split(ls,query_rk(in[x])-1,ls,mid); fa(ls)=fa(mid)=0; pushlazy(mid,val); root=merge(merge(ls,mid),rs); fa(root)=0; } ll query(ll x) { ll ls,rs,ans; split(root,query_rk(in[x]),ls,rs); ans=tree[ls].sum; root=merge(ls,rs); return ans; } }T; void dfs(ll x) { tot++; in[x]=tot; T.insert(w[x],1); for(ll i=head[x];i!=0;i=e[i].nxt) { dfs(e[i].to); } tot++; out[x]=tot; T.insert(-w[x],-1); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,u,v,i; char pd; cin>>n; for(i=2;i<=n;i++) { cin>>u; add(u,i); } for(i=1;i<=n;i++) { cin>>w[i]; } dfs(1); cin>>m; for(i=1;i<=m;i++) { cin>>pd>>u; if(pd=='Q') { cout<<T.query(u)<<endl; } if(pd=='C') { cin>>v; T.change_fa(u,v); } if(pd=='F') { cin>>v; T.update(u,v); } } return 0; }
\(H\) CF825G Tree Queries
-
设 \(dis_{u,v}\) 表示从 \((u,v)\) 间的最小边权,询问等价于求 \(\min\limits_{i=1}^{|black|} \{ dis_{x,black_{i}} \}\) 。
-
不妨钦定第一个黑色节点为根节点,上式等价于 \(\min(dis_{x,black_{1}},\min\limits_{i=2}^{|black|}\{ dis_{black_{i},black_{1}} \})\) 。
-
发现只有黑点会发现影响,直接修改即可。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[2000010],dis[2000010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int fa) { dis[x]=(fa==0)?x:min(dis[fa],x); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); } } } int main() { int n,m,u,v,pd,x,rt=0,ans=0,minn=0x7f7f7f7f,i; scanf("%d%d",&n,&m); for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } for(i=1;i<=m;i++) { scanf("%d%d",&pd,&x); x=(ans+x)%n+1; if(pd==1) { if(rt==0) { rt=x; dfs(rt,0); } minn=min(minn,dis[x]); } else { ans=min(minn,dis[x]); printf("%d\n",ans); } } return 0; }
\(I\) CF1381D The Majestic Brown Tree Snake
\(J\) luogu P2664 树上游戏
\(K\) luogu P5305 [GXOI/GZOI2019] 旧词
-
将询问离线下来,进行扫描线。
-
若 \(k=1\) 的话就是 luogu P4211 [LNOI2014] LCA 了。
-
仍考虑原题中从根节点到 \(x\) 的路径上的点打标记进行树上差分的做法。
-
具体地,设 \(x\) 的权值为 \(c_{x}=dep_{x}^{k}-dep_{fa_{x}}^{k}\) ,那么 \(1 \to x\) 路径上的点权和就是 \(dep_{x}^{k}\) 。
-
在移动右指针 \(r\) 的过程中,需要将 \(1 \to r\) 路径上点 \(x\) 的权值都加上对应的 \(c_{x}\) 。线段树维护每个区间被加了几遍即可(也可以当历史版本和线段树维护最后一个版本和来做)。
点击查看代码
const ll p=998244353; struct node { ll nxt,to; }e[500010]; ll head[500010],fa[500010],siz[500010],son[500010],dep[500010],mi[500010],c[500010],cc[500010],top[500010],dfn[500010],ans[500010],cnt=0,tot=0; vector<pair<ll,ll> >q[500010]; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs1(ll x,ll k) { siz[x]=1; dep[x]=dep[fa[x]]+1; mi[x]=qpow(dep[x],k,p); for(ll i=head[x];i!=0;i=e[i].nxt) { dfs1(e[i].to,k); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } void dfs2(ll x,ll id) { top[x]=id; tot++; dfn[x]=tot; cc[tot]=c[x]; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } struct SMT { struct SegementTree { ll sum,lazy,val; }tree[2000010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].sum=(tree[lson(rt)].sum+tree[rson(rt)].sum)%p; } void build(ll rt,ll l,ll r) { if(l==r) { tree[rt].val=cc[l]; return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); tree[rt].val=(tree[lson(rt)].val+tree[rson(rt)].val)%p; } void pushdown(ll rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].sum=(tree[lson(rt)].sum+tree[rt].lazy*tree[lson(rt)].val%p)%p; tree[rson(rt)].sum=(tree[rson(rt)].sum+tree[rt].lazy*tree[rson(rt)].val%p)%p; tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[rt].lazy=0; } } void update(ll rt,ll l,ll r,ll x,ll y,ll val) { if(x<=l&&r<=y) { tree[rt].sum=(tree[rt].sum+val*tree[rt].val%p)%p; tree[rt].lazy+=val; return; } pushdown(rt); ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } pushup(rt); } ll query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].sum; } pushdown(rt); ll mid=(l+r)/2,ans=0; if(x<=mid) { ans=(ans+query(lson(rt),l,mid,x,y))%p; } if(y>mid) { ans=(ans+query(rson(rt),mid+1,r,x,y))%p; } return ans; } }T; void update(ll u,ll v,ll val,ll n) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { T.update(1,1,n,dfn[top[u]],dfn[u],val); u=fa[top[u]]; } else { T.update(1,1,n,dfn[top[v]],dfn[v],val); v=fa[top[v]]; } } if(dep[u]>dep[v]) { T.update(1,1,n,dfn[v],dfn[u],val); } else { T.update(1,1,n,dfn[u],dfn[v],val); } } ll query(ll u,ll v,ll n) { ll ans=0; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { ans=(ans+T.query(1,1,n,dfn[top[u]],dfn[u]))%p; u=fa[top[u]]; } else { ans=(ans+T.query(1,1,n,dfn[top[v]],dfn[v]))%p; v=fa[top[v]]; } } if(dep[u]>dep[v]) { ans=(ans+T.query(1,1,n,dfn[v],dfn[u]))%p; } else { ans=(ans+T.query(1,1,n,dfn[u],dfn[v]))%p; } return ans; } int main() { ll n,m,k,x,y,i,j; cin>>n>>m>>k; for(i=2;i<=n;i++) { cin>>fa[i]; add(fa[i],i); } for(i=1;i<=m;i++) { cin>>x>>y; q[x].push_back(make_pair(y,i)); } dfs1(1,k); for(i=1;i<=n;i++) { c[i]=(mi[i]-mi[fa[i]]+p)%p; } dfs2(1,1); T.build(1,1,n); for(i=1;i<=n;i++) { update(i,1,1,n); for(j=0;j<q[i].size();j++) { ans[q[i][j].second]=query(q[i][j].first,1,n); } } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
\(L\) luogu P5306 [COCI2018-2019#5] Transport
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18470527,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。