预备知识:
存图的几种方式 链接
欧拉序与dfs序区别
一.ST表+欧拉序
①原理:
欧拉序(前序遍历得到的序列,叫dfs序,但数字可以重复出现,一进一出,叫欧拉序),会发现根结点总在中间,而根结点是该段序列深度最小的点
因此两个点的LCA,就是在该序列上两个点第一次出现的区间内深度最小的那个点
即转化为区间RMQ问题,可以用ST表。当然你可以再写一棵线段树(如果有修改操作)
②实现细节:
主要分为求欧拉序和RMQ两部分。
a.欧拉序:图的遍历(几种存储结构写法不太一样)
cnt:序列长度(每个元素一进一出共两次,记得最大初始化为2*MAXN)
oula[]:欧拉序列,记录编号 dfs前记录一次,dfs后(回溯)再记录一次
depth[]:每个编号的深度(也可以记录每个下标的深度,见注释)
start[]:每个编号第一次出现的序列下标
b.ST表 不懂ST表的,看链接
minl[i][j] 记得第一层初始化为depth[]
pos[][]最值下标,第一层初始化为i
注意这里i是欧拉序列的下标,最终要的是编号(这里经常下标搞混!!)
欧拉序列下标=start【编号】
③代码
a.例题:P3379 被卡常难受,最后还是A了
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define N 500010
vector<int>G[N];
int oula[N<<1],depth[N],start[N];//N*2
int cnt;
int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp; //开20以上就TLE,自闭
//struct node{int deep,order;}minl[N][19];
int n,m,s,x,y;
inline int read()
{
char ch='*';
while(!isdigit(ch=getchar()));
int num=ch-'0';
while(isdigit(ch=getchar()))num=num*10+ch-'0';
return num;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
//depth[cnt]=deep;
if(depth[now]==0)depth[now]=deep;
if(start[now]==0)start[now]=cnt;
int z=G[now].size();
for(int i=0;i<z;i++){
if(G[now][i]!=fa){
dfs(G[now][i],now,deep+1);
oula[++cnt]=now;//出
//depth[cnt]=deep;
//index[now]=cnt;
}
}
}
inline void S_table(){
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
//int l = log2((double)cnt);
for (int j=1;(1<<j)<=cnt;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
//minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
//int k = log2((double)(r-l+1));
int mid=r-(1<<k)+1;
if(minl[l][k]<=minl[mid][k])return pos[l][k];
else return pos[mid][k];
}
int main()
{
n=read();m=read();s=read();
for(int i=1;i<n;++i)
{
x=read();y=read();
G[x].push_back(y);
G[y].push_back(x);
}
dfs(s,-1,1);//求欧拉序列
S_table();//初始化st表
for(int i=1;i<=m;++i)
{
x=read();y=read();
printf("%d\n",oula[rmq(start[x],start[y])]);
}
return 0;
}
b.POJ1310 Nearest Common Ancestors
裸题,只不过多组输入,注意初始化,还有先找根结点
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 100010
vector<int>G[N];
int flag[N];
int oula[N<<1],depth[N],start[N];
int T,cnt;
int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp;
//struct node{int deep,order;}minl[N][30];
int n,m,s,x,y;
inline int read()
{
int x=0;
char c=getchar();
bool flag=0;
while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?-x:x;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
//if(!vis[now])vis[now]=1,
//depth[cnt]=deep;
if(depth[now]==-1)depth[now]=deep;
if(start[now]==-1)start[now]=cnt;
for(int i=0;i<G[now].size();i++){
if(G[now][i]!=fa){
dfs(G[now][i],now,deep+1);
oula[++cnt]=now;//出
//depth[cnt]=deep;
//index[now]=cnt;
}
}
}
inline void S_table(){
//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
int l = log2((double)cnt);
for (int j=1;j<=l;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
//minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
//int k = log2((double)(r-l+1));
int mid=r-(1<<k)+1;
if(minl[l][k]<=minl[mid][k])return pos[l][k];
else return pos[mid][k];
}
int main()
{
scanf("%d",&T);
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;//这个单独初始化
while(T--){
n=read();
memset(flag,0,sizeof(flag));
memset(depth,-1,sizeof(depth));
memset(start,-1,sizeof(start));
memset(oula,0,sizeof(oula)) ;
for(int i=1;i<=n;i++)G[i].clear();
for(int i=1;i<n;++i)
{
x=read();y=read();
flag[y]=1;
cnt=0;
G[x].push_back(y);
G[y].push_back(x);
}
for(int i=1;i<=n;i++) //找根结点
if(!flag[i])
{
s = i;
break;
}
dfs(s,-1,1);
S_table();
//for(int i=1;i<=m;++i)
{
x=read();y=read();
printf("%d\n",oula[rmq(start[x],start[y])]);
}
}
return 0;
}
c.HDU 2586 How far away? 两点距离1 带权值
两点间距离=dis[x]+dis[y]-2*dis[lca]
学习树上前缀和(边权、点权)
#include <bits/stdc++.h>
using namespace std;
#define N 40010 //开大会MLE
struct node{
int t;
int w;
};
vector<node>G[N];
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;
int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp;
int n,m,s,x,y,z;
inline int read()
{
int x=0;
char c=getchar();
bool flag=0;
while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?-x:x;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
if(depth[now]==-1)depth[now]=deep;
if(start[now]==-1)start[now]=cnt;
for(int i=0;i<G[now].size();i++){
if(G[now][i].t!=fa){
//dis[G[now][i].t]=dis[now]+G[now][i].w;
dfs(G[now][i].t,now,deep+G[now][i].w);
oula[++cnt]=now;//出
}
}
}
inline void S_table(){
//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
int l = log2((double)cnt);
for (int j=1;j<=l;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
//minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
int mid=r-(1<<k)+1;
return min(minl[l][k],minl[mid][k]);
//if(minl[l][k]<=minl[mid][k])return pos[l][k];
//else return pos[mid][k];
}
int main()
{
scanf("%d",&T);
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
while(T--){
n=read();
m=read();
memset(flag,0,sizeof(flag));
memset(depth,-1,sizeof(depth));
memset(start,-1,sizeof(start));
memset(oula,0,sizeof(oula)) ;
for(int i=1;i<=n;i++)G[i].clear();
for(int i=1;i<n;++i)
{
x=read();y=read();z=read();
flag[y]=1;
cnt=0;
node tmp1;
tmp1.t=y;
tmp1.w=z;
G[x].push_back(tmp1);
tmp1.t=x;
tmp1.w=z;
G[y].push_back(tmp1);
}
for(int i=1;i<=n;i++)
if(!flag[i])
{
s = i;
break;
}
//cout<<s<<endl;
dfs(s,-1,0);//这里初始deep当dis用,设为0
S_table();
for(int i=1;i<=m;++i)
{
x=read();y=read();
printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
//printf("%d\n",dis[x]+dis[y]-2*dis[rmq(start[x],start[y])]);
}
}
return 0;
}
d. POJ 1986 Distance Queries 两点距离2根任意
#include <iostream>
#include<string.h>
#include<time.h>
#include<map>
#include<stdio.h>
#include<vector>
#include<cmath>
using namespace std;
#define N 80010
struct node{
int t;
int w;
};
vector<node>G[N];
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;
int minl[N<<1][19],pos[N<<1][19],len[N<<1],tmp;
int n,k,m,s,x,y,z;
char o;
inline int read()
{
int x=0;
char c=getchar();
bool flag=0;
while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?-x:x;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
if(depth[now]==-1)depth[now]=deep;
if(start[now]==-1)start[now]=cnt;
for(int i=0;i<G[now].size();i++){
if(G[now][i].t!=fa){
dfs(G[now][i].t,now,deep+G[now][i].w);
oula[++cnt]=now;//出
}
}
}
inline void S_table(){
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
int l = log2((double)cnt);
for (int j=1;j<=l;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
//int k = log2((double)(r-l+1));
int mid=r-(1<<k)+1;
return min(minl[l][k],minl[mid][k]);
}
int main()
{
//scanf("%d",&T);
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
while(~scanf("%d%d",&n,&k)){
cnt=0;
memset(flag,0,sizeof(flag));
memset(depth,-1,sizeof(depth));
memset(start,-1,sizeof(start));
memset(oula,0,sizeof(oula)) ;
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++)G[i].clear();
for(int i=1;i<=k;++i)
{
scanf("%d%d%d %c",&x,&y,&z,&o);
// flag[y]=1;
node tmp1;
tmp1.t=y;
tmp1.w=z;
G[x].push_back(tmp1);
tmp1.t=x;
G[y].push_back(tmp1);
}
// for(int i=1;i<=n;i++)
// if(!flag[i])
// {
// s = i;
// break;
// }
//cout<<s<<endl;
s=1;
dfs(s,-1,0);//这里初始deep=0
S_table();
m=read();
for(int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
//printf("%d\n",oula[rmq(start[x],start[y])]);
}
}
return 0;
}
e.POJ 1470 Closest Common Ancestors 输入格式
记录结点是lca的次数,但是输入消除空格有trick,还有是多组输入!!!答案数组(也可以用map存)每次要初始化(忘了导致wa)
#include <iostream>
#include<string.h>
#include<time.h>
#include<map>
#include<stdio.h>
#include<vector>
#include<cmath>
using namespace std;
#define N 1010
vector<int>G[N];
map<int,int>ans;
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;
int minl[N<<1][19],pos[N<<1][19],len[N<<1],tmp;
int n,m,s,x,y,z;
inline int read()
{
int x=0;
char c=getchar();
bool flag=0;
while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?-x:x;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
if(depth[now]==-1)depth[now]=deep;
if(start[now]==-1)start[now]=cnt;
for(int i=0;i<G[now].size();i++){
if(G[now][i]!=fa){
dfs(G[now][i],now,deep+1);
oula[++cnt]=now;//出
}
}
}
inline void S_table(){
//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
int l = log2((double)cnt);
for (int j=1;j<=l;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
//int k = log2((double)(r-l+1));
int mid=r-(1<<k)+1;
if(minl[l][k]<=minl[mid][k])return pos[l][k];
else return pos[mid][k];
}
int main()
{
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
while(~scanf("%d",&n)){
cnt=0;
memset(flag,0,sizeof(flag));
memset(depth,-1,sizeof(depth));
memset(start,-1,sizeof(start));
memset(oula,0,sizeof(oula)) ;
ans.clear();//清空
for(int i=1;i<=n;i++)G[i].clear();
for(int i=1;i<=n;++i)
{
scanf("%d:(%d)",&x,&z);
while(z--){
scanf("%d",&y);
flag[y]=1;
G[x].push_back(y);
G[y].push_back(x);
}
}
for(int i=1;i<=n;i++)
if(!flag[i])
{
s = i;
break;
}
//cout<<s<<endl;
dfs(s,-1,1);//这里初始deep=1
S_table();
scanf("%d",&m);
for(int i=1;i<=m;++i)
{
while(getchar() != '(');//吸收括号
scanf("%d%d",&x,&y);
while(getchar() != ')');
ans[oula[rmq(start[x],start[y])]]++;
//printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
//printf("%d\n",oula[rmq(start[x],start[y])]);
}
map<int,int>::iterator it=ans.begin();
for(;it!=ans.end();it++){
printf("%d:%d\n",it->first,it->second);
}
}
return 0;
}
f.ZOJ 3195三点距离(样例间的换行)
两两距离的和/2
#include <bits/stdc++.h>
using namespace std;
#define N 50050
struct node{
int t;
int w;
};
vector<node>G[N];
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;
int minl[N<<1][20],pos[N<<1][20],len[N<<1],vis[N],tmp;
int n,m,s,x,y,z;
inline int read()
{
int x=0;
char c=getchar();
bool flag=0;
while(c<'0'||c>'9'){if(c=='-')flag=1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?-x:x;
}
inline void dfs(int now,int fa,int deep){
oula[++cnt]=now;//入
if(depth[now]==-1)depth[now]=deep;
if(start[now]==-1)start[now]=cnt;
for(int i=0;i<G[now].size();i++){
if(G[now][i].t!=fa){
//dis[G[now][i].t]=dis[now]+G[now][i].w;
dfs(G[now][i].t,now,deep+G[now][i].w);
oula[++cnt]=now;//出
}
}
}
inline void S_table(){
//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
int l = log2((double)cnt);
for (int j=1;j<=l;j++){
for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
}
}
}
inline int rmq(int l, int r){
if(l>r) swap(l,r);
int k=len[r-l+1];
//int k = log2((double)(r-l+1));
int mid=r-(1<<k)+1;
return min(minl[l][k],minl[mid][k]);
//if(minl[l][k]<=minl[mid][k])return pos[l][k];
//else return pos[mid][k];
}
int main()
{
for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
int cas=0;
while(~scanf("%d",&n)){
if(cas++)printf("\n");
cnt=0;
memset(flag,0,sizeof(flag));
memset(depth,-1,sizeof(depth));
memset(start,-1,sizeof(start));
memset(oula,0,sizeof(oula)) ;
for(int i=0;i<=n-1;i++)G[i].clear();//下标①
for(int i=1;i<n;++i)
{
x=read();y=read();z=read();
flag[y]=1;
node tmp1;
tmp1.t=y;
tmp1.w=z;
G[x].push_back(tmp1);
tmp1.t=x;
tmp1.w=z;
G[y].push_back(tmp1);
}
for(int i=0;i<=n-1;i++) //下标②
if(!flag[i])
{
s = i;
break;
}
//cout<<s<<endl;
dfs(s,-1,0);//这里初始deep当dis用,设为0
S_table();
m=read();
for(int i=1;i<=m;++i)
{
x=read();y=read();z=read();
printf("%d\n",depth[x]+depth[y]+depth[z]-rmq(start[x],start[y])-rmq(start[y],start[z])-rmq(start[x],start[z]));//三点距离
}
}
return 0;
}
二.倍增法
①先跑深度大的 ②深度相同一起跑,但不同于暴力,每次往上跑2^j次方,注意跑到相同,不一定是最近的祖先
三. Tarjan
Tarjan核心思想:先dfs到叶子→退栈(回溯)后才更新
lca与求强连通写法略有差距:
因为lca侧重记录根结点,需用并查集(有点类似下面说到的low数组),而且需要离线处理询问,dfs的时候“顺便”把询问完成(不然到最后,并查集求得大家祖先都是1,就没意义了)
强连通分量侧重记录具体有谁,要用栈维护,还有low和dfn数组,判断是不是同一个连通分量。
四.树链剖分
待更