RMQ、LCA

コンテンツ

POJ 3264バランスの取れたラインナップ(セグメントツリー、RMQ)

HDU 2586どれくらい離れていますか?(LCAの使用法の詳細な説明)

CSU 1079ツリーに関するクエリ(LCA)


POJ 3264バランスの取れたラインナップ(セグメントツリー、RMQ)

 トピック:

説明

毎日の搾乳では、ファーマージョンの N頭の 牛(1≤N≤50,000   が常に同じ順序で並んでいます。ある日、ファーマージョンは、何頭かの牛と一緒にアルティメットフリスビーのゲームを企画することにしました。物事を単純にするために、彼は搾乳ラインナップから連続した範囲の牛を連れてゲームをプレイします。ただし、すべての牛が楽しむためには、身長の差が大きすぎないようにする必要があります。

ファーマージョンは、  Q  (  1≤Q≤200,000  )の潜在的な牛のグループとその身長(1≤ 身長 ≤1,000,000)のリストを作成しました。各グループについて、彼はあなたの助けを借りて、グループ内で最も短い牛と最も高い牛の身長の違いを判断することを望んでいます。

入力

1行目:2つのスペースで区切られた 整数、  N と Q。2
行 目。N  +  1:  i行目+1には、牛 iの高さ で ある 1 つ整数 が含まれてます 。N2行  目 A から Bまでの牛の範囲を表し  ます。 

出力

行1..Q  各行には、応答への応答であり、範囲内で最も高い牛と最も短い牛の身長の差を示す単一の整数が含まれています。

サンプル入力

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

サンプル出力

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コードを作成するときは、50000がmain関数に表示されず、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;
}

更新操作がないため、更新機能は省略しています。

HDU 2586どれくらい離れていますか?(LCAの使用法の詳細な説明)

 トピック:

説明

村にはn軒の家があり、それらを結ぶいくつかの双方向道路があります。毎日、人々はいつもこのように「家Aから家Bに行きたいのならどこまで行くのか」と尋ねるのが好きです。通常、答えるのは難しいです。しかし、幸いなことに、この村では答えは常にユニークです。道路は、2つの家ごとにユニークなシンプルなパス(「シンプル」は場所を2回訪問できないことを意味します)があるように構築されているためです。あなたの仕事は、これらすべての好奇心旺盛な人々に答えることです。

入力

最初の行は単一の整数T(T <= 10)で、テストケースの数を示します。 
  テストケースごとに、最初の行に2つの数値n(2 <= n <= 40000)とm(1 <= m <= 200)、家の数とクエリの数があります。次のn-1行は、それぞれ3つの数字i、j、kで構成され、1つのスペースで区切られています。つまり、家iと家jを結ぶ道路があり、長さはk(0 <k <= 40000)です。 1からnまでのラベルが付いています。 
  次のm行にはそれぞれ異なる整数iとjがあり、家iと家jの間の距離に答えます。

出力

テストケースごとに、m行を出力します。各行は、クエリの回答を表します。各テストケースの後に、当たり障りのない行を出力します。

サンプル入力

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

2 2
1 2 100
1 2
2 1

サンプル出力

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.深さ優先探索

息子のリストができたので、深さ優先探索を実行できます。

深さ優先を使用して、ツリーをハッシュの一種である1次元配列にマップします。

このテーブルを取得する用途は何ですか?

どの女神がそれを思いついたのかはわかりませんが、最新の共通祖先はこのテーブルのみを使用して取得できます。

たとえば、Bのトラバーサル数は3、Cのトラバーサル数は6、3から6、深さは3 234です。

最小は2で、これはAに対応するため、AはBとCの最も近い共通の祖先です。

トラバーサル番号は必ずしも一意ではないことに注意してください。たとえば、AとIの最も近い共通の祖先を見つけるために、Aには3つの異なるトラバーサル番号があります。

しかし、どちらを選択しても、結果は同じです。

したがって、動的計画法のRMQ法を使用して間隔の最小値を見つけるだけで、共通の祖先を見つけることができます。

4.空間分析

まず、ポイントごとに1つのトラバーサル番号を保存し、1を選択して保存する必要があります。訪問者のサイズはnです

次に、すべてのトラバーサル番号が対応するポイントも保存する必要があります。

では、合計でいくつのトラバーサルがありますか?

ルールは明白で、次のように要約されます。1つのリーフノードには1つのトラバーサル数しかなく、各ノードのトラバーサル数はアウトディグリーに1を加えたものに等しくなります。

したがって、トラバーサルの総数は次のようになります。ノードの総数+アウトディグリーの総数= n + n-1(vi​​sitnumのサイズはn + n-1

最小の最初の次元のサイズはn + n-1で、2番目の次元は約log2(n)+1です

上記の9つのノードには、17のトラバーサルがあります。

5.各ポイントからルートノードまでの距離を計算します。

訪問関数は、深さ優先探索を実装するために再帰的に呼び出される関数です。

検索の過程で、visitnとvisitnumの計算に加えて、deepとdistも計算されます(deepのサイズはnです

(これまでのところ、7つのアレイの目的とサイズは太字の青色でマークされています)

再帰パラメータdとdisを使用して、deepとdistの両方を簡単に計算できます。

6、RMQ

RMQで何を求めているかを間違えないでください。

RMQは、visitnum配列のセクションで最も深い深さを持つポイントに対応するvisitnumを見つけます

ディープの最小値やvisitnumの最小値を探す代わりに。

7、LCA

これはRMQに対応します。これは、visitnum配列のセクションで最も深い深さを持つポイントに対応するvisitnumです。

visitnumによると、どのポイントが最も近い共通の祖先であるかを知る必要があるだけです。

8.クエリ

x、yを入力し、訪問したトラバーサル番号を取り出して、最も近い共通の祖先を見つけます。

トラバーサル番号は多数あるため、1つを選択して、visited配列に格納するだけであることに注意してください。

次に、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)

トピック:

説明

N個の頂点を持つツリーがあり、頂点のラベルは1、2、...、Nです。abk形式のクエリごとに、ポイントkがポイントaからポイントbへのパスに含まれているかどうかに答える必要があります。

入力

入力には、複数のテストデータのセットが含まれています。

テストデータの各セットについて、最初の行には2つの正の整数N、Q(1 <= N、Q <= 10 ^ 5)が含まれ、それぞれツリーに合計N個の頂点があることを示します。Qの質問に答える必要があります。 。次のN-1行では、i番目の行がツリーのi番目のエッジを示しています。これには2つの正の整数xiとyiが含まれており、ポイントxiとポイントyiの間に無向エッジがあることを示しています。次に、合計Q行があり、各行には3つの正の整数a、b、k(1 <= a、b、k <= N)が含まれています。これは、ポイントaからポイントbへのパスにポイントが含まれているかどうかに答える必要があることを示します。 k。

出力

テストデータのセットごとに、各質問に順番に答える必要があります。クエリabkの場合、ポイントaからポイントbへのパスにポイントkが含まれている場合は、「YES」(引用符なし)を出力します。それ以外の場合は、「NO」(引用符なし)を出力します。データの各セットの最後に空白行が出力されます。

サンプル入力

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

サンプル出力

はい
はい
いいえ
はい

ヒント

       データ量が多いため、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