【线段树】P9349 [JOI 2023 Final] Stone Arranging 2|普及+

news2025/5/14 12:38:28

本文涉及知识点

C++线段树

P9349 [JOI 2023 Final] Stone Arranging 2

题目描述

JOI-kun has N N N go stones. The stones are numbered from 1 1 1 to N N N. The color of each stone is an integer between 1 1 1 and 1 0 9 10^9 109, inclusive. In the beginning, the color of Stone i i i ( 1 ≤ i ≤ N 1 \le i \le N 1iN) is A i A_i Ai.

From now, JOI-kun will perform N N N operations. He will put the stones on the table in a line. The operation i i i ( 1 ≤ i ≤ N 1 \le i \le N 1iN) will be performed as follows:

  1. JOI-kun will put Stone i i i on the immediate right of Stone i − 1 i - 1 i1. However, when i = 1 i = 1 i=1, JOI-kun will put Stone 1 on the table.
  2. If there is a stone among Stones 1 , 2 , ⋯   , i − 1 1,2,\cdots,i-1 1,2,,i1 whose current color is the same as Stone i i i, let j j j be the maximum index of such stones, and JOI-kun will paint all of Stones j + 1 , j + 2 , ⋯   , i − 1 j+1,j+2,\cdots,i-1 j+1,j+2,,i1 with the color A i A_i Ai.

In order to confirm whether the operations are correctly performed, JOI-kun wants to know in advance the colors of the stones after all the operations are performed.

Given information of the go stones, write a program which determines the colors of the stones after the N N N operations are performed.

输入格式

Read the following data from the standard input.

N N N
A 1 A_1 A1
A 2 A_2 A2
⋮ \vdots
A N A_N AN

输出格式

Write N N N lines to the standard output. The i i i-th line ( 1 ≤ i ≤ N 1 \le i \le N 1iN) should contain the color of Stone i i i after the N N N operations are performed.

输入输出样例 #1

输入 #1

6
1
2
1
2
3
2

输出 #1

1
1
1
2
2
2

输入输出样例 #2

输入 #2

10
1
1
2
2
1
2
2
1
1
2

输出 #2

1
1
1
1
1
1
1
1
1
2

说明/提示

Samples

Sample 1

The operations are performed as in the following table.

Finally, the colors of Stones 1, 2, 3, 4, 5, 6 will be 1, 1, 1, 2, 2, 2, respectively.

This sample input satisfies the constraints of Subtasks 1, 3.

Sample 2

This sample input satisfies the constraints of all the subtasks.

Constraints

  • 1 ≤ N ≤ 2 × 1 0 5 1 \le N \le 2\times 10^5 1N2×105.
  • 1 ≤ A i ≤ 1 0 9 1 \le A_i \le 10^9 1Ai109 ( 1 ≤ i ≤ N 1 \le i \le N 1iN).
  • Given values are all integers.

Subtasks

  1. (25 points) N ≤ 2000 N \le 2 000 N2000.
  2. (35 points) A i ≤ 2 A_i \le 2 Ai2 ( 1 ≤ i ≤ N 1 \le i \le N 1iN).
  3. (40 points) No additional constraints.

线段树

以下两个变量记录所有已经排列,且不属于任意a[j…i-1]的棋子。
unordered_map<int, vector> mColorIndexs; 键是颜色,值是下标,升序。
set inxs;下标。
线段树记录修改状态。

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	scanf("%d", &n);
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<int N = 12 * 1'000'000>
class COutBuff
{
public:
	COutBuff() {
		m_p = puffer;
	}
	template<class T>
	void write(T x) {
		int num[28], sp = 0;
		if (x < 0)
			*m_p++ = '-', x = -x;

		if (!x)
			*m_p++ = 48;

		while (x)
			num[++sp] = x % 10, x /= 10;

		while (sp)
			*m_p++ = num[sp--] + 48;
	}
	inline void write(char ch)
	{
		*m_p++ = ch;
	}
	inline void ToFile() {
		fwrite(puffer, 1, m_p - puffer, stdout);
	}
private:
	char  puffer[N], * m_p;
};

template<int N = 12 * 1'000'000>
class CInBuff
{
public:
	inline CInBuff() {
		fread(buffer, 1, N, stdin);
	}
	inline int Read() {
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		return f ? -x : x;
	}
private:
	char buffer[N], * S = buffer;
};


template<class TSave, class TRecord >
class CRangUpdateLineTree
{
protected:
	virtual void OnQuery(TSave& ans, const TSave& save, const int& iSaveLeft, const int& iSaveRight) = 0;
	virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) = 0;
	virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) = 0;
	virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) = 0;
};

template<class TSave, class TRecord >
class CVectorRangeUpdateLineTree : public CRangUpdateLineTree<TSave, TRecord>
{
public:
	CVectorRangeUpdateLineTree(int iEleSize, TSave tDefault, TRecord tRecordNull) :m_iEleSize(iEleSize), m_tDefault(tDefault)
		, m_save(iEleSize * 4, tDefault), m_record(iEleSize * 4, tRecordNull) {
		m_recordNull = tRecordNull;
	}
	void Update(int iLeftIndex, int iRightIndex, TRecord value)
	{
		Update(1, 0, m_iEleSize - 1, iLeftIndex, iRightIndex, value);
	}
	TSave Query(int leftIndex, int rightIndex) {
		return Query(leftIndex, rightIndex, m_tDefault);
	}
	TSave Query(int leftIndex, int rightIndex, const TSave& tDefault) {
		TSave ans = tDefault;
		Query(ans, 1, 0, m_iEleSize - 1, leftIndex, rightIndex);
		return ans;
	}
	//void Init() {
	//	Init(1, 0, m_iEleSize - 1);
	//}
	TSave QueryAll() {
		return m_save[1];
	}
	void swap(CVectorRangeUpdateLineTree<TSave, TRecord>& other) {
		m_save.swap(other.m_save);
		m_record.swap(other.m_record);
		std::swap(m_recordNull, other.m_recordNull);
		assert(m_iEleSize == other.m_iEleSize);
	}
protected:
	//void Init(int iNodeNO, int iSaveLeft, int iSaveRight)
	//{
	//	if (iSaveLeft == iSaveRight) {
	//		this->OnInit(m_save[iNodeNO], iSaveLeft);
	//		return;
	//	}
	//	const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
	//	Init(iNodeNO * 2, iSaveLeft, mid);
	//	Init(iNodeNO * 2 + 1, mid + 1, iSaveRight);
	//	this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO * 2], m_save[iNodeNO * 2 + 1], iSaveLeft, iSaveRight);
	//}
	void Query(TSave& ans, int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) {
		if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) {
			this->OnQuery(ans, m_save[iNodeNO], iSaveLeft, iSaveRight);
			return;
		}
		if (iSaveLeft == iSaveRight) {//没有子节点
			return;
		}
		Fresh(iNodeNO, iSaveLeft, iSaveRight);
		const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
		if (mid >= iQueryLeft) {
			Query(ans, iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);
		}
		if (mid + 1 <= iQueryRight) {
			Query(ans, iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight);
		}
	}
	void Update(int iNode, int iSaveLeft, int iSaveRight, int iOpeLeft, int iOpeRight, TRecord value)
	{
		if ((iOpeLeft <= iSaveLeft) && (iOpeRight >= iSaveRight))
		{
			this->OnUpdate(m_save[iNode], iSaveLeft, iSaveRight, value);
			this->OnUpdateRecord(m_record[iNode], value);
			return;
		}
		if (iSaveLeft == iSaveRight) {
			return;//没有子节点
		}
		Fresh(iNode, iSaveLeft, iSaveRight);
		const int iMid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
		if (iMid >= iOpeLeft)
		{
			Update(iNode * 2, iSaveLeft, iMid, iOpeLeft, iOpeRight, value);
		}
		if (iMid + 1 <= iOpeRight)
		{
			Update(iNode * 2 + 1, iMid + 1, iSaveRight, iOpeLeft, iOpeRight, value);
		}
		// 如果有后代,至少两个后代
		this->OnUpdateParent(m_save[iNode], m_save[iNode * 2], m_save[iNode * 2 + 1], iSaveLeft, iSaveRight);
	}
	void Fresh(int iNode, int iDataLeft, int iDataRight)
	{
		if (m_recordNull == m_record[iNode])
		{
			return;
		}
		const int iMid = iDataLeft + (iDataRight - iDataLeft) / 2;
		Update(iNode * 2, iDataLeft, iMid, iDataLeft, iMid, m_record[iNode]);
		Update(iNode * 2 + 1, iMid + 1, iDataRight, iMid + 1, iDataRight, m_record[iNode]);
		m_record[iNode] = m_recordNull;
	}
	vector<TSave> m_save;
	vector<TRecord> m_record;
	TRecord m_recordNull;
	TSave m_tDefault;
	const int m_iEleSize;
};

template<class TSave, class TRecord >
class  CSetMaxLineTree : public CVectorRangeUpdateLineTree<TSave, TRecord>
{
public:
	using CVectorRangeUpdateLineTree<TSave, TRecord>::CVectorRangeUpdateLineTree;
protected:
	virtual void OnQuery(TSave& ans, const TSave& save, const int& iSaveLeft, const int& iSaveRight) {
		ans = max(ans, save);
	}
	virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) {
		save = update;
	}
	virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) {
		par = max(left, r);
	}
	virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord)
	{
		old = newRecord;
	}
};

class Solution {
public:
	vector<int> Ans(const vector<int>& a) {
		const int N = a.size();
		CSetMaxLineTree<int, int> lineTree(N, 0, 0);
		set<int> inxs;
		unordered_map<int, vector<int>> mColorIndexs;
		for (int i = 0; i < N; i++) {
			int j = i;
			if (mColorIndexs.count(a[i]) && mColorIndexs[a[i]].size()) {
				j = mColorIndexs[a[i]].back();
			};

			for (auto it = inxs.lower_bound(i);;) {
				if (inxs.begin() == it) { break; }
				--it;
				if (*it < j) { break; }
				mColorIndexs[a[*it]].pop_back();
			}
			inxs.erase(inxs.lower_bound(j), inxs.lower_bound(i));
			inxs.emplace(i);
			mColorIndexs[a[i]].emplace_back(i);
			lineTree.Update(j, i, a[i]);
		}
		vector<int> ans;
		for (int i = 0; i < N; i++) {
			ans.emplace_back(lineTree.Query(i, i));
		}
		return ans;
	}
};

int main() {	
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	auto a = Read<int>();
	auto res = Solution().Ans(a);
	for (const auto& i : res) {
		cout << i << endl;
	}
	cout << endl;
#ifdef _DEBUG		
	/*printf("T=%d,", T);*/
	Out(a, "a=");
#endif // DEBUG	
	return 0;
}

单元测试

vector<int> a;
		TEST_METHOD(TestMethod11)
		{
			a = {1,2};
			auto res = Solution().Ans(a);
			AssertV({1,2}, res);
		}
		TEST_METHOD(TestMethod12)
		{
			a = { 1,2 ,1};
			auto res = Solution().Ans(a);
			AssertV({ 1,1,1 }, res);
		}
		TEST_METHOD(TestMethod13)
		{
			a = { 4,1,2,3,2,1 };
			auto res = Solution().Ans(a);
			AssertV({4,1,1, 1,1,1 }, res);
		}
		TEST_METHOD(TestMethod14)
		{
			a = { 1,2,1,2,3,2 };
			auto res = Solution().Ans(a);
			AssertV({ 1,1,1,2,2,2 }, res);
		}
		TEST_METHOD(TestMethod15)
		{
			a = { 1,1,2,2,1,2,2,1,1,2 };
			auto res = Solution().Ans(a);
			AssertV({ 1,1,1,1,1,1,1,1,1,2 }, res);
		}

不用线段树

在inxs中存在的下标,颜色是原色,不存在的颜色就是inxs存在的后一个下标的颜色。
即:a[i] = *inxs.lower(i);

class Solution {
		public:
			vector<int> Ans(const vector<int>& a) {
				const int N = a.size();
				set<int> inxs;
				unordered_map<int, vector<int>> mColorIndexs;
				for (int i = 0; i < N; i++) {
					int j = i;
					if (mColorIndexs.count(a[i]) && mColorIndexs[a[i]].size()) {
						j = mColorIndexs[a[i]].back();
					};
					
					for (auto it = inxs.lower_bound(i);;) {
						if (inxs.begin() == it) { break; }
						--it;
						if (*it < j) { break; }
						mColorIndexs[a[*it]].pop_back();
					}
					inxs.erase(inxs.lower_bound(j), inxs.lower_bound(i));
					inxs.emplace(i);
					mColorIndexs[a[i]].emplace_back(i);					;
				}
				vector<int> ans;
				for (int i = 0; i < N; i++) {
					ans.emplace_back(a[*inxs.lower_bound(i)]);
				}
				return ans;
			}
		};

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CodeBuddy终极测评:中国版Cursor的开发革命(含安装指南+HTML游戏实战)

一、腾讯云CodeBuddy产品全景解读 1. 什么是腾讯云代码助手&#xff1f; 官方定义&#xff1a; Tencent Cloud CodeBuddy是由腾讯自研的AI编程辅助工具&#xff0c;基于混元大模型DeepSeek双引擎&#xff0c;提供&#xff1a; ✅ 智能代码补全&#xff08;支持200语言&#x…

从数据中台到数据飞轮:实现数据驱动的升级之路

从数据中台到数据飞轮&#xff1a;实现数据驱动的升级之路 随着数字化转型的推进&#xff0c;数据已经成为企业最重要的资产之一&#xff0c;企业普遍搭建了数据中台&#xff0c;用于整合、管理和共享数据&#xff1b;然而&#xff0c;近年来&#xff0c;数据中台的风潮逐渐减退…

8天Python从入门到精通【itheima】-1~5

目录 1节&#xff1a; 1.Python的优势&#xff1a; 2.Python的独具优势的特点&#xff1a; 2节-初识Python&#xff1a; 1.Python的起源 2.Python广泛的适用面&#xff1a; 3节-什么是编程语言&#xff1a; 1.编程语言的作用&#xff1a; 2.编程语言的好处&#xff1a;…

T2000云腾边缘计算盒子在数猪场景中的应用|YOLOv8+NodeRED

在现代养猪业蓬勃发展的当下&#xff0c;养殖场的智能化管理成为提升效率与精准度的关键所在。而养猪场盘点工作一直是养殖场管理中的重要环节&#xff0c;传统的盘点方式不仅耗费大量人力、时间&#xff0c;还容易出现误差。如今&#xff0c;T2000 云腾边缘计算盒子的出现&…

Baklib内容中台构建全攻略

内容中台构建路径全解析 企业构建内容中台需遵循“战略驱动-系统搭建-持续优化”的三阶段路径。首先明确业务目标与知识资产类型&#xff0c;通过显性知识结构化将分散内容转化为标准化数字资产&#xff0c;依托四库体系&#xff08;知识库、资源库、模板库、规则库&#xff0…

爬虫工具与编程语言选择指南

有人问爬虫如何选择工具和编程语言。根据我多年的经验来说&#xff0c;是我肯定得先分析不同场景下适合的工具和语言。 如果大家不知道其他语言&#xff0c;比如JavaScript&#xff08;Node.js&#xff09;或者Go&#xff0c;这些在特定情况下可能更合适。比如&#xff0c;如果…

系统平衡与企业挑战

在复杂的系统中&#xff0c;一切都在寻找平衡&#xff0c;而这个平衡从不静止。它在不断的变化与反馈中调整&#xff0c;以适应外界环境的变动。就像一个企业&#xff0c;它无法完全回避变化&#xff0c;但却总是在挑战中寻找新的平衡点。 最近遇到一家企业&#xff0c;引入了…

征程 6 yolov5s-rgb-nhwc 量化指南

在 征程 6 平台&#xff0c;我们可以按照这个方式编译 input_typr_rt 为 rgb&#xff0c;且 layout 为 NHWC 的模型。这样做的好处是&#xff0c;当用户的数据输入源本身就是 NHWC 的 rgb 图像时&#xff0c;这么做可以避免额外的数据处理操作。这里以 yolov5s 为例进行介绍。 …

国产化Word处理控件Spire.Doc教程:如何使用 C# 从 Word 中提取图片

通过编程方式从 Word 文档中提取图片&#xff0c;可以用于自动化文档处理任务。E-iceblue旗下Spire系列产品是国产文档处理领域的优秀产品&#xff0c;支持国产化&#xff0c;帮助企业高效构建文档处理的应用程序。本文将演示如何使用 C# 和 Spire.Doc for .NET 库从 Word 文件…

Telnet 类图解析

Telnet 类图&#xff08;文本描述&#xff09; --------------------------------------- | Telnet | --------------------------------------- | - host: str | # 目标主机 | - port: int …

PowerShell 实现 conda 懒加载

问题 执行命令conda init powershell会在 profile.ps1中添加conda初始化的命令。 即使用户不需要用到conda&#xff0c;也会初始化conda环境&#xff0c;拖慢PowerShell的启动速度。 解决方案 本文展示了如何实现conda的懒加载&#xff0c;默认不加载conda环境&#xff0c;只…

笔记项目 day02

一、用户登录接口 请求参数&#xff1a; 用loginDTO来封装请求参数&#xff0c;要加上RequestBody注解 响应参数&#xff1a; 由于data里内容较多&#xff0c;考虑将其封装到一个LoginUser的实体中&#xff0c;用户登陆后&#xff0c;需要生成jwtToken并返回给前端。 登录功…

国鑫主板bios切换显示模式为独立显卡

# 进入到Platform Miscellaneous Configuration Active Video 切换为PCIE Device保存退出&#xff01; 如果之前有安装过nvidia驱动&#xff0c;记得卸载掉再安装一遍。

【日撸 Java 300行】Day 14(栈)

目录 Day 14&#xff1a;栈 一、栈的基本知识 二、栈的方法 1. 顺序表实现栈 2. 入栈 3. 出栈 三、代码及测试 拓展&#xff1a; 小结 Day 14&#xff1a;栈 Task&#xff1a; push 和 pop 均只能在栈顶操作.没有循环, 时间复杂度为 O(1). 一、栈的基本知识 详细的介…

2025最新出版 Microsoft Project由入门到精通(七)

目录 优化资源——在资源使用状况视图中查看资源的负荷情况 在资源图表中查看资源的负荷情况 优化资源——资源出现冲突时的原因及处理办法 资源过度分类的处理解决办法 首先检查任务工时的合理性并调整 增加资源供给 回到资源工作表中双击对应的过度分配资源 替换资…

修改(替换)文件中的指定内容并保留文件修改前的时间(即修改前后文件的最后修改时间保持不变)

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 修改&#xff08;替换&#xff09;文件中的指…

应用探析|千眼狼PIV测量系统在职业病防治中的应用

1、职业病防治背景 随着《职业病防治法》及各省市“十四五”职业病防治规划的深入推进&#xff0c;工作场所粉尘危害监测与防控已成为疾控部门的核心任务。以矿山、建材、冶金、化工等行业为例&#xff0c;粉尘浓度、分布及传播特性的精准测量是评估职业病风险的关键。 传统的…

nvidia驱动更新-先卸载再安装-ubuntu

显卡驱动升级前&#xff0c;卸载旧版本&#xff0c;可采用两种方式。 1.命令行 &#xff08;1&#xff09;查找已安装的 NVIDIA 驱动和相关包&#xff1a;dpkg -l | grep nvidia &#xff08;2&#xff09;完全卸载 NVIDIA 驱动&#xff1a;sudo apt remove purge nvidia-*…

推荐算法工程化:ZKmall模板商城的B2C 商城的用户分层推荐策略

在 B2C 电商竞争激烈的市场环境中&#xff0c;精准推荐已成为提升用户体验、促进商品销售的关键。ZKmall 模板商城通过推荐算法工程化手段&#xff0c;深度挖掘用户数据价值&#xff0c;制定科学的用户分层推荐策略&#xff0c;实现 “千人千面” 的个性化推荐&#xff0c;帮助…

基于Java和PostGIS的AOI面数据球面面积计算实践

目录 前言 一、计算方法简介 二、球面面积计算 1、AOI数据转Polygon 2、Geotools面积计算 3、GeographicLib面积计算 4、PostGIS面积计算 三、结果分析 1、不同算法结果对比 2、与互联网AOI对比 3、与天地图测面对比 四、总结 前言 在现代地理信息系统&#xff08;G…