RMQ、LCA

目录

POJ 3264 Balanced Lineup(线段树、RMQ)

HDU 2586 How far away?(LCA使用详解)

CSU 1079 树上的查询 (LCA)


POJ 3264 Balanced Lineup(线段树、RMQ)

 题目:

Description

For the daily milking, Farmer John's N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.

Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.

Input

Line 1: Two space-separated integers, N and Q
Lines 2.. N+1: Line i+1 contains a single integer that is the height of cow i 
Lines N+2.. NQ+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.

Output

Lines 1.. Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.

Sample Input

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

Sample Output

6
3
0

题意:

给出一个数列和若干查询

输出查询区间内最大值和最小值的差值

这个题目可以用线段树来做,也可以用RMQ来做

RMQ代码:

//为什么数组第二个维度开16是错的,17是对的。。。
#include<iostream>
using namespace std;

int maxs[50000][17];
int mins[50000][17];

int main()
{
	int n, q, k, low, high, rmax, rmin;
	cin >> n >> q;
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &maxs[i][0]);
		mins[i][0] = maxs[i][0];
	}
	for (int j = 1; (1 << j) <= n; j++)
	{
		for (int i = 0; i < n; i++)
		{
			maxs[i][j] = maxs[i][j - 1];
			mins[i][j] = mins[i][j - 1];
			k = i + (1 << (j - 1));
			if (k < n && maxs[i][j] < maxs[k][j - 1])maxs[i][j] = maxs[k][j - 1];
			if (k < n && mins[i][j] > mins[k][j - 1])mins[i][j] = mins[k][j - 1];
		}
	}
	while (q--)
	{
		scanf("%d%d", &low, &high);
		low--;
		high--;
		int j = 0;
		while ((1 << j) <= high - low)j++;
		j--;
		rmax = maxs[high + 1 - (1 << j)][j];
		if (rmax < maxs[low][j])rmax = maxs[low][j];
		rmin = mins[high + 1 - (1 << j)][j];
		if (rmin > mins[low][j])rmin = mins[low][j];
		printf("%d\n", rmax - rmin);
	}
	return 0;
}

写RMQ的代码要注意,main函数里面完全没有出现50000,也没有出现17,关于越界的一切计算都必须靠n,和数组开多大无关。当然,数组一定要足够大。

线段树代码:

#include<iostream>
using namespace std;

int num[50001];
int maxx[200001];
int minn[200001];

void build(int key, int low, int high)
{
	if (low == high)
	{
		minn[key] = maxx[key] = num[low];
		return;
	}
	int mid = (low + high) / 2;
	build(key * 2, low, mid);
	build(key * 2 + 1, mid + 1, high);
	maxx[key] = (maxx[key * 2] > maxx[key * 2 + 1]) ? maxx[key * 2] : maxx[key * 2 + 1];
	minn[key] = (minn[key * 2] < minn[key * 2 + 1]) ? minn[key * 2] : minn[key * 2 + 1];
}

int querymaxx(int key, int low, int high, int x, int y)
{
	if (low == x && high == y)return maxx[key];
	int mid = (low + high) / 2;
	if (mid < x)return querymaxx(key * 2 + 1, mid + 1, high, x, y);
	if (mid >= y)return querymaxx(key * 2, low, mid, x, y);
	int a = querymaxx(key * 2, low, mid, x, mid);
	int b = querymaxx(key * 2 + 1, mid + 1, high, mid + 1, y);
	return  (a>b) ? a : b;
}

int queryminn(int key, int low, int high, int x, int y)
{
	if (low == x && high == y)return minn[key];
	int mid = (low + high) / 2;
	if (mid < x)return queryminn(key * 2 + 1, mid + 1, high, x, y);
	if (mid >= y)return queryminn(key * 2, low, mid, x, y);
	int a = queryminn(key * 2, low, mid, x, mid);
	int b = queryminn(key * 2 + 1, mid + 1, high, mid + 1, y);
	return  (a<b) ? a : b;
}

int main()
{
	int n, q, low, high;
	cin >> n >> q;
	for (int i = 1; i <= n; i++)scanf("%d", &num[i]);
	build(1, 1, n);
	while (q--)
	{
		scanf("%d%d", &low, &high);
		printf("%d\n", querymaxx(1, 1, n, low, high) - queryminn(1, 1, n, low, high));
	}
	return 0;
}

因为没有更新操作,所以省掉了update函数。

HDU 2586 How far away?(LCA使用详解)

 题目:

Description

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input

First line is a single integer T(T<=10), indicating the number of test cases. 
  For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n. 
  Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

Output

For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

Sample Input

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

2 2
1 2 100
1 2
2 1

Sample Output

10
25
100
100

题意:
n个房子用n-1条路连接起来(也就是一棵树),对于每一个询问,

求出2个房子直接的线路距离

思路:

这个题目只要建立一个树,然后查询任意2个点之间的距离,没有更新操作,所以可以用LCA来做。

LCA就是寻找最近公共祖先,这有什么用呢?

这是因为有一个性质,假设B和C的最近公共祖先是A,那么对于整个树的根节点D,

都有:|BD|+|CD|-|AD|*2=|BC|

也就是说,只要事先求出所有点到D的距离dist(dist的大小为n),

然后对于输入的B和C,只需要求出最近公共祖先,即可利用上式得到答案。

步骤:

1,存边

每次输入1条边,把2个点都存入对方的儿子列表中

因为儿子的数量未知,但是总数是2n-2,所以用向量数组v来存比较合适。(v的大小为n

2,建树

建树的方法有很多,而且n个点都是可以用来当做根节点的。

这里我采用的是以第一个节点作为根节点,利用深度优先搜索建树

因为在计算dist之前需要找到根节点,所以需要1个数组fa记录父亲的标号(fa的大小为n

3,深度优先搜索

既然有儿子列表,那么就可以深度优先搜索了。

利用深度优先,把树映射到一维数组,这是哈希的一种。

得到这个表有什么用呢?

也不知道是哪个神人想出来的,仅用这个表就可以得到最近公共祖先。

比如B的遍历数为3,C的遍历数为6,从3到6,深度依次为3 2 3 4,

最小的是2,对应的是A,所以A就是B和C的最近公共祖先。

注意,遍历数不一定是唯一的,比如求A和I的最近公共祖先,A有3个不同的遍历数,

但是不管选哪个,结果都是一样的。

所以说,只需要用动态规划的RMQ方法求出区间的最小值,即可求出公共祖先。

4,空间分析

首先,我们需要对每个点存1个遍历数,任选1个存起来即可。(visitn的大小为n

然后,我们还需要把所有的遍历数对应的是哪个点存起来。

那么,一共有多少个遍历数呢?

规律很明显,总结如下:1个叶子节点只有1个遍历数,每个节点的遍历数等于出度加1

所以遍历数一共有:节点总数+出度总数=n+n-1(visitnum的大小为n+n-1

mins第一个维度的大小为n+n-1,第二个维度约为log2(n)+1

上面的9个节点就有17个遍历数。

5,计算每个点到根节点的距离。

visit函数是一个递归调用的函数,用来实现深度优先搜索。

搜索的过程中,除了要计算visitn和visitnum,还要计算deep和dist(deep的大小为n

(至此,7个数组的用途和大小都用蓝色粗体标注了)

deep和dist都可以利用递归的参数d和dis非常方便的计算出来。

6,RMQ

用RMQ求的是什么,这个倒是不要搞错了。

RMQ求的是visitnum数组的一段区间中,拥有最小deep的那个点对应的visitnum

而不是求deep的最小值,也不是求visitnum的最小值。

7,LCA

这个和RMQ对应,求的是visitnum数组的一段区间中,拥有最小deep的那个点对应的visitnum。

只需要根据visitnum便可知道到底哪个点是最近公共祖先

8,查询

输入x,y,取出他们的遍历数visitn,由此求出他们的最近公共祖先。

需要注意的是,因为遍历数有很多个,随便取一个存到visitn数组中,

那么x和y的遍历数谁大谁小就完全不知道了,需要判断。

代码:

#include<iostream>
#include<vector>
using namespace std;

struct node
{
	int son;
	int distance;
};

int n;
vector<node>v[40001];//存儿子标号
int deep[40001];//每个点的深度
int visitnum[80001];//遍历数是2*n-1
int visitn[40001];//每个点的任意一个遍历数
int vnum;
int mins[80001][18];		//区间最小值
int dist[40001];		//每个点到祖先的距离distance
int fa[40001];

void visit(int m, int d, int dis)		//遍历重编号、计算distance
{
	vector<node>::iterator p;
	deep[m] = d;
	dist[m] = dis;
	for (p = v[m].begin(); p != v[m].end(); p++)
	{
		if (fa[(*p).son]>-1)continue;
		fa[(*p).son] = m;
		visitnum[vnum++] = m;	//存入访问的第vnum个点是哪个点
		visit((*p).son, d + 1, dis + (*p).distance);
	}
	visitn[m] = vnum;		//注意这2句的顺序
	visitnum[vnum++] = m;
}

void rmq()		//计算区间最小值
{
	for (int i = 1; i <= 2 * n - 1; i++)mins[i][0] = visitnum[i];
	for (int j = 1; (1 << j) <= 2 * n - 1; j++)
	{
		for (int i = 1; i <= 2 * n - 1; i++)
		{
			mins[i][j] = mins[i][j - 1];
			int k = i + (1 << (j - 1));
			if (k <= 2 * n - 1 && deep[mins[i][j]] > deep[mins[k][j - 1]])
				mins[i][j] = mins[k][j - 1];
		}
	}
}

int lca(int x, int y)	//求最近公共祖先
{
	x = visitn[x], y = visitn[y];
	if (x > y)x ^= y ^= x ^= y;
	int j = 0;
	while ((1 << j) <= y - x + 1)j++;
	j--;
	int min = mins[y + 1 - (1 << j)][j];
	if (deep[min] > deep[mins[x][j]])min = mins[x][j];
	return min;
}

int main()
{
	int t, m, x, y, l;
	cin >> t;
	while (t--)
	{
		cin >> n >> m;
		vnum = 1;
		for (int i = 1; i <= n; i++)
		{
			v[i].clear();	//初始化
			fa[i] = -1;
		}
		for (int i = 1; i < n; i++)
		{
			scanf("%d%d%d", &x, &y, &l);
			node nod1, nod2;
			nod1.distance = l, nod1.son = y;
			v[x].insert(v[x].end(), nod1);
			nod2.distance = l, nod2.son = x;
			v[y].insert(v[y].end(), nod2);
		}
		fa[1] = 1;
		visit(1, 1, 0);
		rmq();
		while (m--)
		{
			scanf("%d%d", &x, &y);
			printf("%d\n", dist[x] + dist[y] - dist[lca(x, y)] * 2);
		}
	}
	return 0;
}

CSU 1079 树上的查询 (LCA)

题目:

Description

现有一棵有N个顶点的树,顶点的标号分别为1, 2, …, N。对于每个形如a b k的询问,你需要回答在从点a到点b的路径上是否包含点k。

Input

输入包含多组测试数据。

对于每组测试数据,第一行包含两个正整数N, Q (1<=N, Q<=10^5),分别表示这棵树一共有N个顶点,接下来你需要回答Q个询问。接下来N-1行中,第i行描述了这棵树的第i条边:包含两个正整数xi、yi,表示点xi和点yi之间有一条无向边。再接下来一共Q行,每行均包含三个正整数a、b、k (1<=a, b, k<=N),表示你需要回答在从点a到点b的路径上是否包含点k。

Output

对于每组测试数据,你需要按顺序依次回答每个询问。对于任意一个询问a b k,如果点a到点b的路径上包含点k,则输出“YES”(不包括引号),否则输出“NO”(不包括引号)。每组数据结尾输出一个空行。

Sample Input

3 4
1 2
3 2
2 3 2
2 1 1
1 2 3
1 3 2

Sample Output

YES
YES
NO
YES

Hint

       由于数据量较大,推荐使用scanf/printf。

代码:

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;

int n;
vector<int>v[100001];//存儿子标号
int deep[100001];//每个点的深度
int visitnum[200001];//遍历数是2*n-1
int visitn[100001];//每个点的任意一个遍历数
int vnum;
int mins[200001][20];		//区间最小值
int fa[100001];

void visit(int m, int d)		//遍历重编号
{
	vector<int>::iterator p;
	deep[m] = d;
	for (p = v[m].begin(); p != v[m].end(); p++)
	{
		if (fa[*p]>-1)continue;
		fa[*p] = m;
		visitnum[vnum++] = m;	//存入访问的第vnum个点是哪个点
		visit(*p, d + 1);
	}
	visitn[m] = vnum;		//注意这2句的顺序
	visitnum[vnum++] = m;
}

void rmq()		//计算区间最小值
{
	for (int i = 1; i <= 2 * n - 1; i++)mins[i][0] = visitnum[i];
	for (int j = 1; (1 << j) <= 2 * n - 1; j++)
	{
		for (int i = 1; i <= 2 * n - 1; i++)
		{
			mins[i][j] = mins[i][j - 1];
			int k = i + (1 << (j - 1));
			if (k <= 2 * n - 1 && deep[mins[i][j]] > deep[mins[k][j - 1]])
				mins[i][j] = mins[k][j - 1];
		}
	}
}

int lca(int x, int y)	//求最近公共祖先
{
	x = visitn[x], y = visitn[y];
	if (x > y)x ^= y ^= x ^= y;
	int j = 0;
	while ((1 << j) <= y - x + 1)j++;
	j--;
	int min = mins[y + 1 - (1 << j)][j];
	if (deep[min] > deep[mins[x][j]])min = mins[x][j];
	return min;
}

int main()
{
	int q, x, y, k;
	bool fl = false;
	while (scanf("%d%d", &n, &q) != EOF)
	{
		if (fl)printf("\n");
		fl = true;
		vnum = 1;
		for (int i = 1; i <= n; i++)
		{
			v[i].clear();	//初始化
			fa[i] = -1;
		}
		for (int i = 1; i < n; i++)
		{
			scanf("%d%d", &x, &y);
			v[x].insert(v[x].end(), y);//存入儿子的标号
			v[y].insert(v[y].end(), x);//存入儿子的标号
		}
		fa[1] = 1;
		visit(1, 1);		
		rmq();
		while (q--)
		{
			scanf("%d%d%d", &x, &y, &k);
			int lca1 = lca(x, y), lca2 = lca(x, k), lca3 = lca(y, k);
			bool flag = (lca1 == lca2 && lca3 == k);
			if (lca1 == lca3 && lca2 == k)flag = true;
			if (flag)printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/nameofcsdn/article/details/122852661