acwing提高LCA

最近公共祖先(LCA,Lowest Common Ancestor)

1172. 祖孙询问(倍增LCA)

在这里插入图片描述

输入样例:

10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
233 12
233 13
233 15
233 19

输出样例:

1
0
0
0
2

代码模板

//倍增法 求 LCA 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 40010, M = N * 2;

int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];
int q[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

//预处理 
void bfs(int root)
{
    memset(depth, 0x3f, sizeof depth);
    depth[0] = 0, depth[root] = 1; //0 -> 哨兵 
    int hh = 0, tt = 0;
    q[0] = root;
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (depth[j] > depth[t] + 1)
            {
                depth[j] = depth[t] + 1;
                q[ ++ tt] = j;
                fa[j][0] = t;
                for (int k = 1; k <= 15; k ++ )
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];  //核心 
            }
        }
    }
}

int lca(int a, int b)
{
    if (depth[a] < depth[b]) swap(a, b);
    
    //使 a 与 b 处于同一深度 
    for (int k = 15; k >= 0; k -- )  
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    if (a == b) return a;
    
    //a 与 b 同时往上跳直到跳到最近公共祖先的下一个节点 
    for (int k = 15; k >= 0; k -- )
        if (fa[a][k] != fa[b][k])  //核心 -> 跳到公共祖先之下, 因为跳过了最近公共祖先, if成立 0 == 0 
        {
            a = fa[a][k];
            b = fa[b][k];
        }
    return fa[a][0];
}

int main()
{
    scanf("%d", &n);
    int root = 0;
    memset(h, -1, sizeof h);
	
	//输入建图 
    for (int i = 0; i < n; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if (b == -1) root = a;
        else add(a, b), add(b, a);
    }
    
    //预处理 depth 和 fa 数组 
    bfs(root);

    scanf("%d", &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int p = lca(a, b);
        if (p == a) puts("1");
        else if (p == b) puts("2");
        else puts("0");
    }

    return 0;
}


 

1171. 距离(Tarjan-离线求LCA)

在这里插入图片描述

输入样例:

3 2
1 2 10
3 1 15
1 2
3 2

输出样例:

10
25

代码模板

//Tarjan-离线求LCA
//树的节点和节点的distance必定经过最近公共祖先(LCA) 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

const int N = 10010, M = N * 2;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int p[N];
int res[M];
int st[N];
vector<PII> query[N];   // first存查询的另外一个点,second存查询编号

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

//递归-预处理dist数组 
void dfs(int u, int fa)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        dist[j] = dist[u] + w[i];
        dfs(j, u);
    }
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void tarjan(int u)
{
    st[u] = 1;  //表示正在访问 
    //对u这条路上的根节点的左下的点用并查集合并到根节点 
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!st[j])   // 0 表示还没有访问过 
        {
            tarjan(j); // dfs搜到底 
            p[j] = u;  //搜过的节点合并到根节点 
        }
    }
		
    for (auto item : query[u])
    {
        int y = item.first, id = item.second; // y节点 和节点编号 
        if (st[y] == 2) // 如果需要询问的点已经是左下的点 
        {
            int anc = find(y); //获得询问点y的根节点, 即最近公共祖先 
            res[id] = dist[u] + dist[y] - dist[anc] * 2;
        }
    }

    st[u] = 2;   //访问结束并且已经回溯 
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if (a != b)
        {
            query[a].push_back({b, i});   
            query[b].push_back({a, i});
        }
    }

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    dfs(1, -1);   //任意节点为父节点 
    tarjan(1);

    for (int i = 0; i < m; i ++ ) printf("%d\n", res[i]);

    return 0;
}

 

356. 严格次小生成树(Kruskal + 倍增法LCA)

在这里插入图片描述

输入样例:

5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

输出样例:

11

代码模板

//严格最小生成树 时间复杂度 O(mlogn) 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int N = 100010, M = 300010, INF = 0x3f3f3f3f;
typedef long long LL;
int n, m;
int h[N], ne[M], e[M], w[M], idx;
int p[N];
int fa[N][20], d1[N][20], d2[N][20], depth[N];
struct edges{
	int a, b, w;
	bool f;
	bool operator < (const edges &e ) const
	{
		return w < e.w;
	}
}ed[M];

void add(int a, int b, int c)
{
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; 
}

int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

LL kruskal()
{
	for(int i = 1; i <= n; i ++ ) p[i] = i;
	sort(ed, ed + m);
	LL res = 0;
	for(int i = 0; i < m; i ++ )
	{
		int a = ed[i].a, b = ed[i].b, c = ed[i].w;
		a = find(a), b= find(b);
		if(a != b)
		{
			p[a] = b;
			ed[i].f = true;
			res += c;
		}
	}
	return res;
}

void built()
{
	for(int i = 0; i < m; i ++ )
	{
		if(ed[i].f)
		{
			int a = ed[i].a, b = ed[i].b, c = ed[i].w;
			add(a, b, c), add(b, a, c);
		}
	}
}

/*
预处理数组
depth[i] 节点 i 的深度 
对 depth bfs同时可以求得 
fa[i][j] 从i开始跳 2^j步的节点
d1[i][j] 节点 i 开始跳 2^j步范围内的最大值 
d2[i][j] 节点 i开始跳 2^j 步范围内的次大值 
*/ 
void bfs()
{
	memset(depth, 0x3f, sizeof depth);
	queue<int> q;
	q.push(1); //取树种任意节点作为父节点 
	depth[1] = 1, depth[0] = 0;  // 0 -> 哨兵 
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		for(int i = h[t]; ~i; i = ne[i])
		{
			int j = e[i];
			if(depth[j] > depth[t] + 1)
			{
				depth[j] = depth[t] + 1;
				q.push(j);
				fa[j][0] = t;  // j的父节点为 t 
				d1[j][0] = w[i], d2[j][0] = -INF;  //d1:j到父节点的最大值 d2:j到父节点的次大值 
				for(int k = 1; k <= 16; k ++ )  //倍增法 倍增范围内的最大值和次大值d1,d2 
				{
					int anc = fa[j][k - 1];
					fa[j][k] = fa[anc][k - 1];
					int distance[4] = {d1[j][k - 1], d1[anc][k - 1], d2[j][k - 1], d2[anc][k - 1]};
					for(int u = 0; u < 4; u ++ )
					{
						int x = distance[u];
						if(x > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = x;
						else if(x > d2[j][k] && x != d1[j][k]) d2[j][k] = x;
					}
				} 
			}
		}
	} 
	 
}

//用非树边代替 a - b 的最大值或者次大值
int loc(int a, int b, int w)
{
	//倍增查询,查询过程种记录路径中的最大值和次大值 
	static int distance[N * 2];
	int cnt = 0;
	if(depth[a] < depth[b]) swap(a, b);
	
	//使 a 和 b 处于同一深度 
	for(int k = 16; k >= 0; k -- ) //可以抽象成二进制位 
	{
		if(depth[fa[a][k]] >= depth[b])
		{
			distance[cnt ++] = d1[a][k];
			distance[cnt ++] = d2[a][k];
			a = fa[a][k];			
		}

	}
	
	//a 和 b 同时向上跳 直到 a == b即找到公共祖节点 
	if(a != b)
	{
		for(int k = 16; k >=0; k -- )
		{
			if(fa[a][k] != fa[b][k])  // 注意: 如果跳过了公共祖节点会都等于0 if不成立 
			{
				distance[cnt ++] = d1[a][k];
				distance[cnt ++] = d2[a][k];
				distance[cnt ++] = d1[b][k];
				distance[cnt ++] = d2[b][k];
				a = fa[a][k], b = fa[b][k];
			}
			distance[cnt ++ ] = d1[a][0];
        	distance[cnt ++ ] = d1[b][0];
		}
	}
	
	int dist1 = 0, dist2 = 0;
	//重新整理得到 a - b 的最大值和次大值 
	for(int i = 0; i < cnt; i ++ )
	{
		if(distance[i] > dist1) dist2 = dist1, dist1 = distance[i];
        else if (distance[i] != dist1 && distance[i] > dist2) dist2 = distance[i];
	}
	
	if(w > dist1) return w - dist1;
	if(w > dist2) return w - dist2;
	
}
int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for(int i = 0; i < m; i ++ )
	{
		int a, b, w;
		cin >> a >> b >> w;
		ed[i] = {a, b, w};
	}
	
	LL res = kruskal(); //得到最小生成树 
	built();  //最小生成树建图 
	bfs();    //预处理数组fa, d1, d2, loc;
	
	LL sum = 1e18;
	//枚举所有非树边
	for(int i = 0; i < m; i ++ )
	{
		if(!ed[i].f)
		{
			sum = min(sum, res + loc(ed[i].a, ed[i].b, ed[i].w));
		}
	}
	
	cout << sum << endl;
	
	return 0;
	
}


 

输入样例:


输出样例:


代码模板



 

posted @ 2022-03-22 14:56  panse·  阅读(41)  评论(0)    收藏  举报