/**
* ST, RMQ, LCA, TarJan类算法:
-
ST 算法
-
RMQ(区间最值查询)-- 区间dp预处理
-
LCA(最近公共祖先)-- 树上的最短路
-
TarJan (缩点,割边,顶点 ....)算法
*/
第一. RMQ + ST
练习题+题解:
1. 洛谷 P1816 忠诚题 + 题解
2. 洛谷 P1440 求m区间内的最小值 + 题解
5.P3865 【模板】ST 表 + 快速读取
** RMQ算法: 首先弄懂f[i][j] 表示什么意思? – i表示从第i个开始, j 表示2 的 j 次方;
即 : f数组的意思是以i为起点2的j次方长度的区间的极值是多少*
eg:
f[1][1] = {1-2}f[1][0]与f[2][0];
f[1][2] = {1-5}f[1][1]与f[3][1];
j 表示为从 j-1表示位得到最快
模板RMQ + ST表:
首先要ST预处理:(相当于初始化) 时间复杂度O(nlgn);*
void ST(int n){
for(int i = 1; i <= n; i ++){
f[i][0] = a[i]; // 初始化, 每一个从i开始2^0就是本身
}
for(int j = 1; 1<<j <= n; j ++){
for(int i = 1; i+1<<j-1 <= n; i ++){
// 相当于动态规划方程
// 这里有一个技巧:j 次方 从 j-1 次方中获得, 上面的0次方就是为了这里
f[i][j] = (min)/(max)(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
}
查询 O(1) 注意只能只能查询不可以维护
int RMQ(int l, int r){
int k = log2(r-l+1);
// 这要比较一下, 是防止中间数据丢失 l, 与 r-(1<<k)+1
return (max)/(min)(f[l][k], f[r-(1<<k)+1][j-1]
}
第二. LCA + ST
练习题+题解:
3. P3379 【模板】最近公共祖先(LCA)
模板LCA:(查询最近公共祖先节点)
第一类: 直接爬树型:单独的LCA:
第一步邻接表存储节点(模拟树):
邻接表vector<int >g[maxn] 存储
第二步 一直遍历找到父节点
void dfs(int u){
int sz = g[u].size();
for(int i = 0; i < sz; i ++){
int v = g[u][i];
if(v == p[u]) continue;
p[v] = u;
dfs(v);
}
}
第三步LCA:
int LCA(int x, int y){
// 用一个vis[]数组来标记
memset(vis, 0, sizeof(vis));
//先让x标记完
while(x){
vis[x] = true;
x = p[x];
}
//此时遍历y时, 如果已经标记,则说明x也有此处的父节点
while(!vis[y])
y = p[y];
// 退出循环,返回y 值 即:最近公共祖先节点
return y;
}
第二类:LCA + ST表类型 + 邻接矩阵:
第一步:邻接矩阵存储
int u, v;
for(int i = 1; i < n; i ++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
或者:
定义:
struct node{
int v, next;
}tree[N<<1];
这样存储
void add(int x, int y){
tree[++ cnt].v = y;
tree[cnt].next = head[x];
head[x] = cnt;
return;
}
重点理解, 转移方程:
第二步:dfs/bfs遍历得到深度,f[][]数组(父节点数组)
void dfs(int fa, int x){
vis[x] = 1;// 标记
depth[x] = depth[fa] + 1;
f[x][0] = fa;// 以x为起点,向上走1就是fa节点,记录下来
//循环来得到f[x][i];
for(int i = 1; i < 20; i ++){
f[x][i] = f[f[x][i-1]][i-1];
}
for(int i = head[x]; i ; i = tree[i].next){
if(!vis[tree[i].v]){
dfs(x, tree[i].v); // 向下遍历
}
}
vis[x] = 0;
return ;
}
第三步:LCA
int LCA(int x, int y){
// 保证x的深度不小于y
if(depth[x] < depth[y]){
swap(x, y);
}
int z = 1 << 19;
for(int i = 19; i >= 0; i--){
if(depth[x]-z >= depth[y]){
x = f[x][i];
}
z >>= 1;
}
if(x == y){
return x;
}
// 同一深度的
for(int i = 19; i >= 0; i --){
if(f[x][i]!=f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
**第三类:**计算树上两节点距离型:
练习题+题解:
4.P3884 [JLOI2009]二叉树问题
大致算法与上面第二类相同;
u 到 最近公共祖先节点距离 + v 到 最近公共祖先节点距离
重点算法 Tarjan算法:
练习题+题解
[6. P2863 USACO06JAN
算法总介: Tarjan算法,是一个基于Dfs的算法, 求得一般是强连通分量
重点题
***里面要用的内容了解:
- low[] 数组: 为i或i的子树能够追溯到的最早的栈中节点的次序号, 也是最小的;
2dfn[ ] : 在DFS中该节点被搜索的次序(时间戳), 收索的顺序; - 邻接表 有两种写法:
(1)vector g[maxn]; // 存地图
(2)struct node {
int v,next;
}edge[1001];
4.stack st 容器, 记录此次dfs()遍历的点,用于回溯;
5.in[maxn]数组 记录有没有在队列里面***
int key = 0;
模板1:tarjan(u){
dfn[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值
st.push(u) // 将节点u压入栈中
in[u] = true; // 在栈里面
int sz = g[u].size();
for(int i = 0; i < sz; i ++)// 枚举每一条边
int v = g[u][i];
if (!dfn[v]) // 如果节点v未被访问过
tarjan(v) // 继续向下找
low[u] = min(low[u], low[v])
else if (in[v]) // 如果节点u还在栈内
low[u] = min(low[u], dfn[v])
if (dfn[u] == Low[u]){ // 如果节点u是强连通分量的根
int v;
key ++;
do{
v = st.top();
st.pop();
c[v] = key;
in[v] = 0;
b[key] += a[v];
}while(v != u);
// 将v退栈,为该强连通分量中一个顶点
}
}
模板2:
void Tarjan ( int x ) {
dfn[ x ] = ++dfs_num ;
low[ x ] = dfs_num ;
vis [ x ] = true ;//是否在栈中
stack [ ++top ] = x ;
for ( int i=head[ x ] ; i!=0 ; i=e[i].next ){
int temp = e[ i ].to ;
if ( !dfn[ temp ] ){
Tarjan ( temp ) ;
low[ x ] = gmin ( low[ x ] , low[ temp ] ) ;
}
else if ( vis[ temp ])low[ x ] = gmin ( low[ x ] , dfn[ temp ] ) ;
}
if ( dfn[ x ]==low[ x ] ) {//构成强连通分量
vis[ x ] = false ;
color[ x ] = ++col_num ;//染色
while ( stack[ top ] != x ) {//清空
color [stack[ top ]] = col_num ;
vis [ stack[ top-- ] ] = false ;
}
top -- ;
}
}