ST,RMQ,LCA, Tarjan类算法总结

/**


* 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(n
lgn);*

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


7. Tarjan 割线 P1656 炸铁路

算法总介: Tarjan算法,是一个基于Dfs的算法, 求得一般是强连通分量

重点题

G. How Many Paths? + 强连通

***里面要用的内容了解:

  1. low[] 数组: 为i或i的子树能够追溯到的最早的栈中节点的次序号, 也是最小的;
    2dfn[ ] : 在DFS中该节点被搜索的次序(时间戳), 收索的顺序;
  2. 邻接表 有两种写法:
    (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 -- ;
         }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值