轻重链剖分求LCA
- 轻重链剖分,就是将一棵树划分为多个互不相交的链
- 两遍
,复杂度
维护出该节点的直接父亲,在通过其儿子节点中重量最大的(即以其为根的子树大小最大)作为重儿子,也就是该点所在树链的直接儿子,同时维护深度
维护每条链的链头,用于处理不在同一条链上的两点求
,实现直接跳过一条链
- 若两点在一条链上,则输出深度较低的点
- 若两点不在一条链上,则两点中链头深度较大往上跳,直到两个跳到同一条链上
- 复杂度
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
//implements Runnable
public class Main implements Runnable{//利用修改线程,避免栈溢出
static long md=(long)998244353;
static long Linf=Long.MAX_VALUE/2;
static int inf=Integer.MAX_VALUE/2;
static int N=500000;
static
class Edge{
int fr,to,nxt;
long val;
public Edge(int u,int v,long w) {
fr=u;
to=v;
val=w;
}
}
static int[] fa=new int[N+1];//该点在链上的直接父亲
static int[] son=new int[N+1];//该点在链上的直接儿子(重儿子)
static int[] siz=new int[N+1];//以该点为根的子树大小
static int[] dep=new int[N+1];//深度
static int[] top=new int[N+1];//该点所在链的链头
static
class Tp{
Edge[] e;
int[] head;
int cnt;
public Tp() {
e=new Edge[N*2+1];
head=new int[N+1];
cnt=0;
}
void addEdge(int fr,int to,long val) {
cnt++;
e[cnt]=new Edge(fr, to, val);
e[cnt].nxt=head[fr];
head[fr]=cnt;
}
void dfs1(int x) {
siz[x]=1;
dep[x]=dep[fa[x]]+1;
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
long w=e[i].val;
if(v==fa[x])continue;
fa[v]=x;
dfs1(v);
siz[x]+=siz[v];
if(son[x]==0||siz[son[x]]<siz[v])
son[x]=v;
}
}
void dfs2(int x,int tp) {
top[x]=tp;
if(son[x]!=0)dfs2(son[x], tp);
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
if(v==fa[x]||v==son[x])continue;
dfs2(v, v);
}
}
int LCA(int x,int y) {
while(top[x]!=top[y]) {//直到跳到一条链上
if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
if(dep[x]<dep[y])return x;
else return y;
}
}
static Tp rel=new Tp();
static void solve() throws Exception{
AReader input=new AReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int n=input.nextInt();
int m=input.nextInt();
int s=input.nextInt();
for(int i=1;i<n;++i) {
int u=input.nextInt();
int v=input.nextInt();
rel.addEdge(u, v, 0);
rel.addEdge(v, u, 0);
}
rel.dfs1(s);
rel.dfs2(s, s);
while(m>0) {
int x=input.nextInt();
int y=input.nextInt();
out.println(rel.LCA(x, y));
m--;
}
out.flush();
out.close();
}
// public static void main(String[] args) throws Exception{
//本地调试用这个main,因为改线程后不会报错,会直接停止
// solve();
// }
public static final void main(String[] args) throws Exception {
//本地调试将这个和implements Runnable去掉
new Thread(null, new Main(), "线程名字", 1 << 27).start();
}
@Override
public void run() {
try {
//原本main函数的内容
solve();
} catch (Exception e) {
}
}
static
class AReader{
BufferedReader bf;
StringTokenizer st;
BufferedWriter bw;
public AReader(){
bf=new BufferedReader(new InputStreamReader(System.in));
st=new StringTokenizer("");
bw=new BufferedWriter(new OutputStreamWriter(System.out));
}
public String nextLine() throws IOException{
return bf.readLine();
}
public String next() throws IOException{
while(!st.hasMoreTokens()){
st=new StringTokenizer(bf.readLine());
}
return st.nextToken();
}
public char nextChar() throws IOException{
//确定下一个token只有一个字符的时候再用
return next().charAt(0);
}
public int nextInt() throws IOException{
return Integer.parseInt(next());
}
public long nextLong() throws IOException{
return Long.parseLong(next());
}
public double nextDouble() throws IOException{
return Double.parseDouble(next());
}
public float nextFloat() throws IOException{
return Float.parseFloat(next());
}
public byte nextByte() throws IOException{
return Byte.parseByte(next());
}
public short nextShort() throws IOException{
return Short.parseShort(next());
}
public BigInteger nextBigInteger() throws IOException{
return new BigInteger(next());
}
public void println() throws IOException {
bw.newLine();
}
public void println(int[] arr) throws IOException{
for (int value : arr) {
bw.write(value + " ");
}
println();
}
public void println(int l, int r, int[] arr) throws IOException{
for (int i = l; i <= r; i ++) {
bw.write(arr[i] + " ");
}
println();
}
public void println(int a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(int a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(String a) throws IOException{
bw.write(a);
bw.newLine();
}
public void print(String a) throws IOException{
bw.write(a);
}
public void println(long a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(long a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(double a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(double a) throws IOException{
bw.write(String.valueOf(a));
}
public void print(char a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(char a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
}
}
虚树
- 通过关键点建树,删去关键点影响不到的点,减少树形
遍历点的个数
- 单调栈维护
- 实际单调栈中维护了一条树链
- 根先入栈,然后关键点按照
序依次入栈
- 设当前加入的关键点为
,当前栈顶为
,求其
- 若
,则
在单调栈维护的树链上,直接入栈
- 若
,则弹出栈顶,并将
与新栈顶连边,然后接着弹栈顶
- 若当前栈顶的
序小于
的
序,则说明栈中没有
,则将
入栈,弹出的栈顶与其连边
- 然后将
入栈
- 最后,再将单调栈中维护的树链,进行连边(依次弹出,并对新栈顶连边)
- 至此,虚树建立完毕,然后在虚树上跑树形
- 注意,只用在虚树上对该点进行完
后,
,实现清空
例题:
消耗战


解题思路
- 先得出树形
,表示不能到
最少需要的代价
- 若当前点是关键点,则只能选择
,使
不能到达
- 若不是,则还可以选择
可以到达该点,然后在后续不能到达关键点的代价,取最小的
- 发现查询很多次,若直接跑则为
,
- 然后发现,总共回访问到的点数
- 考虑对于每次询问建立虚树,不相干点不访问
- 注意虚树要开2倍空间
- 边值
,最终结果回爆
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
//implements Runnable
public class Main implements Runnable{
static long md=(long)998244353;
static long Linf=Long.MAX_VALUE/2;
static int inf=Integer.MAX_VALUE/2;
static int N=500000;
static
class Edge{
int fr,to,nxt;
long val;
public Edge(int u,int v,long w) {
fr=u;
to=v;
val=w;
}
}
static long[] minv=new long[N+1];
static int[] dfn=new int[N+1];
static int dfn_tim=0;
static int[] fa=new int[N+1];
static int[] son=new int[N+1];
static int[] siz=new int[N+1];
static int[] dep=new int[N+1];
static int[] top=new int[N+1];
static
class Tp{
Edge[] e;
int[] head;
int cnt;
public Tp() {
e=new Edge[N*2+1];
head=new int[N+1];
cnt=0;
}
void addEdge(int fr,int to,long val) {
cnt++;
e[cnt]=new Edge(fr, to, val);
e[cnt].nxt=head[fr];
head[fr]=cnt;
}
void dfs1(int x) {
siz[x]=1;
dep[x]=dep[fa[x]]+1;
dfn_tim++;
dfn[x]=dfn_tim;
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
long w=e[i].val;
if(v==fa[x])continue;
fa[v]=x;
minv[v]=Math.min(minv[x], w);
dfs1(v);
siz[x]+=siz[v];
if(son[x]==0||siz[son[x]]<siz[v])
son[x]=v;
}
}
void dfs2(int x,int tp) {
top[x]=tp;
if(son[x]!=0)dfs2(son[x], tp);
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
if(v==fa[x]||v==son[x])continue;
dfs2(v, v);
}
}
int LCA(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
if(dep[x]<dep[y])return x;
else return y;
}
}
static Tp rel=new Tp();
static
class Node{
int x;
int y;
public Node(int u,int v) {
x=u;
y=v;
}
}
static int k=0;
static Node[] h=new Node[N+1];
static boolean[] isk=new boolean[N+1];
static
class Tv{
Edge[] e;
int[] head;
int cnt;
public Tv() {
e=new Edge[N*2+1];
head=new int[N+1];
cnt=0;
}
void addEdge(int fr,int to) {
cnt++;
e[cnt]=new Edge(fr, to, 0);
e[cnt].nxt=head[fr];
head[fr]=cnt;
}
void Make_vi() {
Stack<Integer> st=new Stack<Integer>();
Arrays.sort(h,1,k+1,(o1,o2)->{
return o1.y-o2.y;
});
st.add(1);
for(int i=1;i<=k;++i) {
int u=h[i].x;
int p=rel.LCA(st.peek(), u);
while(st.peek()!=p) {
int pre=st.peek();st.pop();
if(dfn[st.peek()]<dfn[p]){
st.add(p);
}
addEdge(st.peek(), pre);
}
st.add(u);
}
while(st.peek()!=1) {
int pre=st.peek();
st.pop();
addEdge(st.peek(), pre);
}
}
long Tree_Dp(int x) {
long sum=0;
long res=0;
for(int i=head[x];i>0;i=e[i].nxt) {
sum+=Tree_Dp(e[i].to);
}
if(isk[x])res=minv[x];
else res=Math.min(minv[x], sum);
isk[x]=false;
head[x]=0;
return res;
}
}
static Tv vir=new Tv();
static void solve() throws Exception{
AReader input=new AReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int n=input.nextInt();
for(int i=1;i<n;++i) {
int u=input.nextInt();
int v=input.nextInt();
long w=input.nextLong();
rel.addEdge(u, v, w);
rel.addEdge(v, u, w);
}
minv[1]=Linf;
rel.dfs1(1);
rel.dfs2(1,1);
int m=input.nextInt();
while(m>0) {
k=input.nextInt();
for(int i=1;i<=k;++i) {
int x=input.nextInt();
isk[x]=true;
if(h[i]==null)h[i]=new Node(x, dfn[x]);
else {
h[i].x=x;
h[i].y=dfn[x];
}
}
vir.Make_vi();
out.println(vir.Tree_Dp(1));
m--;
}
out.flush();
out.close();
}
// public static void main(String[] args) throws Exception{
// solve();
// }
public static final void main(String[] args) throws Exception {
new Thread(null, new Main(), "线程名字", 1 << 27).start();
}
@Override
public void run() {
try {
//原本main函数的内容
solve();
} catch (Exception e) {
}
}
static
class AReader{
BufferedReader bf;
StringTokenizer st;
BufferedWriter bw;
public AReader(){
bf=new BufferedReader(new InputStreamReader(System.in));
st=new StringTokenizer("");
bw=new BufferedWriter(new OutputStreamWriter(System.out));
}
public String nextLine() throws IOException{
return bf.readLine();
}
public String next() throws IOException{
while(!st.hasMoreTokens()){
st=new StringTokenizer(bf.readLine());
}
return st.nextToken();
}
public char nextChar() throws IOException{
//确定下一个token只有一个字符的时候再用
return next().charAt(0);
}
public int nextInt() throws IOException{
return Integer.parseInt(next());
}
public long nextLong() throws IOException{
return Long.parseLong(next());
}
public double nextDouble() throws IOException{
return Double.parseDouble(next());
}
public float nextFloat() throws IOException{
return Float.parseFloat(next());
}
public byte nextByte() throws IOException{
return Byte.parseByte(next());
}
public short nextShort() throws IOException{
return Short.parseShort(next());
}
public BigInteger nextBigInteger() throws IOException{
return new BigInteger(next());
}
public void println() throws IOException {
bw.newLine();
}
public void println(int[] arr) throws IOException{
for (int value : arr) {
bw.write(value + " ");
}
println();
}
public void println(int l, int r, int[] arr) throws IOException{
for (int i = l; i <= r; i ++) {
bw.write(arr[i] + " ");
}
println();
}
public void println(int a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(int a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(String a) throws IOException{
bw.write(a);
bw.newLine();
}
public void print(String a) throws IOException{
bw.write(a);
}
public void println(long a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(long a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(double a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(double a) throws IOException{
bw.write(String.valueOf(a));
}
public void print(char a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(char a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
}
}
大工程

解题思路
- 先考虑树形
- 对于每个点得出,以该点为根的子树中,任意两个关键点的最长链和最短链
- 对于每个点维护离其子树中关键点最远
和最近的距离
- 设当前点为
,依次访问其儿子,后面的向前连,一定不会漏
- 对于每个儿子
考虑,以
为根的子树中有无关键点
- 若有,则可以通过
点去访问
子树中的其他关键点(包括
自身),更新最长链和最短链
- 这样相连的关键点一定是最短路径(两点通过其lca相连)
- 发现
与
同阶,则考虑建虚树,同上题用单调栈
package Tx;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
//implements Runnable
public class Tx2{
static long md=(long)998244353;
static long Linf=Long.MAX_VALUE/2;
static int inf=Integer.MAX_VALUE/2;
static int N=500000;
static
class Edge{
int fr,to,nxt;
long val;
public Edge(int u,int v,long w) {
fr=u;
to=v;
val=w;
}
}
static int[] fa=new int[N+1];
static int[] son=new int[N+1];
static int[] siz=new int[N+1];
static int[] dep=new int[N+1];
static int[] top=new int[N+1];
static int[] dfn=new int[N+1];
static int dfn_tim=0;
static
class Tp{
Edge[] e;
int[] head;
int cnt;
public Tp() {
e=new Edge[N*2+1];
head=new int[N+1];
cnt=0;
}
void addEdge(int fr,int to,long val) {
cnt++;
e[cnt]=new Edge(fr, to, val);
e[cnt].nxt=head[fr];
head[fr]=cnt;
}
void dfs1(int x) {
siz[x]=1;
dep[x]=dep[fa[x]]+1;
dfn_tim++;
dfn[x]=dfn_tim;
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
long w=e[i].val;
if(v==fa[x])continue;
fa[v]=x;
dfs1(v);
siz[x]+=siz[v];
if(son[x]==0||siz[son[x]]<siz[v])
son[x]=v;
}
}
void dfs2(int x,int tp) {
top[x]=tp;
if(son[x]!=0)dfs2(son[x], tp);
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
if(v==fa[x]||v==son[x])continue;
dfs2(v, v);
}
}
int LCA(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
if(dep[x]<dep[y])return x;
else return y;
}
}
static Tp rel=new Tp();
static long[] fmx=new long[N+1];
static long[] gmi=new long[N+1];
static int[] sz=new int[N+1];
static long sum=0;
static long mi=inf;
static long mx=-inf;
static
class Node{
int x;
int y;
public Node(int u,int v) {
x=u;
y=v;
}
}
static int k=0;
static Node[] h=new Node[N+1];
static boolean[] isk=new boolean[N+1];
static
class Tv{
Edge[] e;
int[] head;
int cnt;
public Tv() {
e=new Edge[N*2+1];
head=new int[N+1];
cnt=0;
}
void addEdge(int fr,int to,long val) {
cnt++;
e[cnt]=new Edge(fr, to, val);
e[cnt].nxt=head[fr];
head[fr]=cnt;
}
void Make_Vir() {
Stack<Integer> st=new Stack<Integer>();
Arrays.sort(h,1,k+1,(o1,o2)->{
return o1.y-o2.y;
});
st.add(1);
for(int i=1;i<=k;++i) {
int u=h[i].x;
int p=rel.LCA(u, st.peek());
while(p!=st.peek()) {
int pre=st.peek();st.pop();
if(dfn[st.peek()]<dfn[p]) {
st.add(p);
}
addEdge(st.peek(), pre, dep[pre]-dep[st.peek()]);
}
st.add(u);
}
while(st.peek()!=1) {
int pre=st.peek();st.pop();
addEdge(st.peek(), pre, dep[pre]-dep[st.peek()]);
}
}
void Tree_dp(int x) {
if(isk[x]) {
sz[x]=1;
fmx[x]=0;
gmi[x]=0;
}else {
sz[x]=0;
fmx[x]=0;
gmi[x]=inf;
}
for(int i=head[x];i>0;i=e[i].nxt) {
int v=e[i].to;
long w=e[i].val;
Tree_dp(v);
sum+=w*(k-sz[v])*sz[v];
if(sz[x]>0) {
mi=Math.min(mi, gmi[x]+w+gmi[v]);
mx=Math.max(mx, fmx[x]+w+fmx[v]);
}
fmx[x]=Math.max(fmx[x], fmx[v]+w);
gmi[x]=Math.min(gmi[x], gmi[v]+w);
sz[x]+=sz[v];
}
isk[x]=false;
head[x]=0;
}
}
static Tv vir=new Tv();
static void solve() throws Exception{
AReader input=new AReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int n=input.nextInt();
for(int i=1;i<n;++i) {
int u=input.nextInt();
int v=input.nextInt();
rel.addEdge(u, v, 0);
rel.addEdge(v, u, 1);
}
rel.dfs1(1);
rel.dfs2(1, 1);
int m=input.nextInt();
while(m>0) {
sum=0;
mx=-inf;
mi=inf;
k=input.nextInt();
for(int i=1;i<=k;++i) {
int x=input.nextInt();
isk[x]=true;
if(h[i]==null)h[i]=new Node(x, dfn[x]);
else {
h[i].x=x;
h[i].y=dfn[x];
}
}
vir.Make_Vir();
vir.Tree_dp(1);
out.println(sum+" "+mi+" "+mi);
m--;
}
out.flush();
out.close();
}
public static void main(String[] args) throws Exception{
solve();
}
// public static final void main(String[] args) throws Exception {
// new Thread(null, new Main(), "线程名字", 1 << 27).start();
// }
// @Override
// public void run() {
// try {
// //原本main函数的内容
// solve();
//
// } catch (Exception e) {
// }
// }
static
class AReader{
BufferedReader bf;
StringTokenizer st;
BufferedWriter bw;
public AReader(){
bf=new BufferedReader(new InputStreamReader(System.in));
st=new StringTokenizer("");
bw=new BufferedWriter(new OutputStreamWriter(System.out));
}
public String nextLine() throws IOException{
return bf.readLine();
}
public String next() throws IOException{
while(!st.hasMoreTokens()){
st=new StringTokenizer(bf.readLine());
}
return st.nextToken();
}
public char nextChar() throws IOException{
//确定下一个token只有一个字符的时候再用
return next().charAt(0);
}
public int nextInt() throws IOException{
return Integer.parseInt(next());
}
public long nextLong() throws IOException{
return Long.parseLong(next());
}
public double nextDouble() throws IOException{
return Double.parseDouble(next());
}
public float nextFloat() throws IOException{
return Float.parseFloat(next());
}
public byte nextByte() throws IOException{
return Byte.parseByte(next());
}
public short nextShort() throws IOException{
return Short.parseShort(next());
}
public BigInteger nextBigInteger() throws IOException{
return new BigInteger(next());
}
public void println() throws IOException {
bw.newLine();
}
public void println(int[] arr) throws IOException{
for (int value : arr) {
bw.write(value + " ");
}
println();
}
public void println(int l, int r, int[] arr) throws IOException{
for (int i = l; i <= r; i ++) {
bw.write(arr[i] + " ");
}
println();
}
public void println(int a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(int a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(String a) throws IOException{
bw.write(a);
bw.newLine();
}
public void print(String a) throws IOException{
bw.write(a);
}
public void println(long a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(long a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(double a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
public void print(double a) throws IOException{
bw.write(String.valueOf(a));
}
public void print(char a) throws IOException{
bw.write(String.valueOf(a));
}
public void println(char a) throws IOException{
bw.write(String.valueOf(a));
bw.newLine();
}
}
}
博客介绍了轻重链剖分求LCA和虚树的相关知识。轻重链剖分将树划分为链,可高效求LCA;虚树通过关键点建树,减少遍历点个数。还给出了消耗战和大工程两道例题的解题思路,均考虑建立虚树优化复杂度。
177万+

被折叠的 条评论
为什么被折叠?



