当前位置: 首页 > news >正文

【扫描线 线段树】P1856 [IOI 1998 ] [USACO5.5] 矩形周长Picture|普及+

本文涉及知识点

C++线段树

[IOI 1998 ] [USACO5.5] 矩形周长Picture

题目背景

墙上贴着许多形状相同的海报、照片。它们的边都是水平和垂直的。每个矩形图片可能部分或全部的覆盖了其他图片。所有矩形合并后的边长称为周长。

题目描述

编写一个程序计算周长。

如图 1 1 1 所示 7 7 7 个矩形。

如图 2 2 2 所示,所有矩形的边界。所有矩形顶点的坐标都是整数。

输入格式

输入文件的第一行是一个整数 N N N,表示有多少个矩形。接下来 N N N 行给出了每一个矩形左下角坐标和右上角坐标。

输出格式

输出文件只有一个正整数,表示所有矩形的周长。

样例 #1

样例输入 #1

7
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4
2 15 10 22
30 10 36 20
34 0 40 16

样例输出 #1

228

提示

数据范围及约定

对于全部数据, 1 ≤ N < 5000 1 \le N<5000 1N<5000,所有坐标的数值范围都在 − 10 4 -10^4 104 10 4 10^4 104 之间。

扫描线+线段树(错误或过于复杂)

单个矩形的周长<1e5,所有矩形的周长小于5e8,在整数范围内。
本题用到加最大值线段树。
本题比较复杂,就慢慢试:先假定宽和高都大于1。

最终图形竖直边界

性质一:一定是原始的竖直边界。
性质二:如果在某个矩形内部则一定不是竖直边界。
用线段树记录当前扫描线(列)的相关信息:
i1:当前行在几条原始竖直边界上。
i2:当前行在几个矩形内部。矩形(x1,y1,x2,y2)的内部指的是(x1+1,y1+1,x2-1,y2-1),以下简称内部矩形。
i3:i1 > 0 且0==i2,i3等于当前节点的长度;否则为0。
枚举某列的过程如下:
一,任意当前列的原始竖直边界(y1,y2),线段树[y1,y2]的i1++。
二,任意内部矩形左边界(y1,y2),线段树[y1,y2]的i2++。
三,任意原始矩形的右边界(y1,y2),线段树[y1+1,y2-1]的i2–。
四,统计整个扫描线的i3。
五,任意当前列的原始竖直边界(y1,y2),线段树[y1,y2]的i1–。

最终图形的水平边界

某些点是水平边界和竖直边界的结合部。可能会重复计算。内部矩形改成:(x1,y1+1,x2,y2-1)。
一个点,再竖直边界上,无论是否在内部,都无需枚举。

改进

i1和i2可以合并,矩形内部-10000,边缘+1。

树状数组+扫描线

如果(x,y)在任意矩形内部或边缘为1,否则为0。
性质一:如果(x-1,y)是0,(x,y)是1。则(x,y)是左边缘。同理(x,y)是1,(x+1,y)是0右边缘。
性质二:任意一行左边缘的点数等于右边缘的点数。令(x-1,y)是0,(x,y)是1,(x…x2,y)全部是1,(x2+1,y)是0。则(x2,y)是对应的右边缘。
任意矩形:(x1,y1,x2,y2)
处理第x1列是:[y1,y2]++。
处理第x2+1列时:[y1,y2]–。

求右边缘

乘以2就是左右边缘。
列从小到处理,先加后减。处理完++之后,处理–之前,old记录当前列为0的数,处理完后,iNew记录当前列为0的点数。old和iNew之间各点的数量,只会正数变成更小的正数,或变成0。不会0变负数。位了简便,每处理一条边就出来:ans += max(0,iNew-old)
线段树节点,存储数据:
i1:则区间最少被几个矩形覆盖。
i2:i1的点数。
默认{0,0}。
缓存数据:
add,默认0。
因为初始化时错误的:所有各点都设置为0。
注意
宽和高都是左闭右开空间。比如:(0,0,1,1),x 和y都只能取值0,不能取值1。
(0,0,2,2)周长是8,上边界(0,0),(0,1),右边界(0,2),(1,2),无需处理重复。

代码

核心代码

#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(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_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);}void Query(int leftIndex, int rightIndex) {Query(1, 0, m_iEleSize - 1, leftIndex, rightIndex);}//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(int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) {if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) {this->OnQuery(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(iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);}if (mid + 1 <= iQueryRight) {Query(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;const int m_iEleSize;
};typedef pair<int, int> TSave;
typedef int  TRecord;
class  CMyLineTree : public  CVectorRangeUpdateLineTree<TSave, TRecord>
{
public:int m_ans = 0;using CVectorRangeUpdateLineTree::CVectorRangeUpdateLineTree;
protected:virtual void OnQuery(const TSave& save, const int& iSaveLeft, const int& iSaveRight) {}virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) override{save.first += update;if (0 == save.second) {save.second = iSaveRight - iSaveLeft + 1;}}virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight)  override{if (left.first == r.first) {par.first = left.first;par.second = left.second + r.second;}else if (left.first < r.first) {par = left;}else {par = r;}}virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) override{old += newRecord;}
};//P1856 [IOI 1998 ] [USACO5.5] 矩形周长Picture
class Solution {
public:int Ans(vector<tuple<int, int, int, int>>& a) {int iMinL = INT_MAX, iMinT = INT_MAX, iMaxR = INT_MIN, iMaxB = INT_MIN;for (const auto& [left, t, r, b] : a) {iMinL = min(iMinL, left);iMinT = min(iMinT, t);iMaxR = max(iMaxR, r);iMaxB = max(iMaxB, b);}for (auto& [left, t, r, b] : a) {left -= iMinL;r -= iMinL;t -= iMinT;b -= iMinT;}m_iMaxY = iMaxB - iMinT;m_iMaxX = iMaxR - iMinL;return (DoH(a) + DoV(a)) * 2;}int DoH(const vector<tuple<int, int, int, int>>& a)const {vector <tuple<int, int, int, int>> xky1y2;for (const auto& [left, t, r, b] : a) {xky1y2.emplace_back(left, -1, t, b - 1);xky1y2.emplace_back(r, 1, t, b - 1);}sort(xky1y2.begin(), xky1y2.end());CMyLineTree lineTree(m_iMaxY + 1, { 0,0 }, 0);for (int i = 0; i <= m_iMaxY; i++) {lineTree.Update(i, i, 0);}int ans = 0;int pre = m_iMaxY + 1;for (const auto& [x, kid, y1, y2] : xky1y2) {lineTree.Update(y1, y2, -kid);auto root = lineTree.QueryAll();int cur = root.second * (0 == root.first);ans += max(0, cur - pre);swap(pre, cur);}return ans;}int DoV(const vector<tuple<int, int, int, int>>& a)const {vector <tuple<int, int, int, int>> ykxx;for (const auto& [left, t, r, b] : a) {ykxx.emplace_back(t, -1, left, r - 1);ykxx.emplace_back(b, 1, left, r - 1);}sort(ykxx.begin(), ykxx.end());CMyLineTree lineTree(m_iMaxX + 1, { 0,0 }, 0);for (int i = 0; i <= m_iMaxX; i++) {lineTree.Update(i, i, 0);}int ans = 0;int pre = m_iMaxX + 1;for (const auto& [y, kid, x1, x2] : ykxx) {lineTree.Update(x1, x2, -kid);auto root = lineTree.QueryAll();int cur = root.second * (0 == root.first);ans += max(0, cur - pre);swap(pre, cur);}return ans;}int m_iMaxX, m_iMaxY;
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUG		auto a = Read<tuple<int,int,int,int>>();auto res = Solution().Ans(a);cout << res << endl;
#ifdef _DEBUG	Out(a, "a=");/*Out(que, ",que="); */
#endif // DEBUG	return 0;
}

单元测试

	vector<tuple<int, int, int, int>> a;TEST_METHOD(TestMethod1){a = { {0,0,1,1} };auto res = Solution().Ans(a);AssertEx(4, res);}TEST_METHOD(TestMethod11){a = { {0,0,2,3} };auto res = Solution().Ans(a);AssertEx(10, res);}TEST_METHOD(TestMethod12){a = { {0,0,4,4},{0,4,4,8} };auto res = Solution().Ans(a);AssertEx(24, res);}TEST_METHOD(TestMethod13){a = { {0,0,4,4},{2,2,6,6} };auto res = Solution().Ans(a);AssertEx(24, res);}TEST_METHOD(TestMethod14){a = { {0,6,20,16},{10,14,35,31},{30,2,39,20},{15,0,31,10},{17,21,25,28},{45,16,51,26},{49,6,55,22} };auto res = Solution().Ans(a);AssertEx(228, res);}TEST_METHOD(TestMethod15){	a = { {30,0,2255,390},{1740,385,2255,2320},{0,1825,1745,2320},{0,495,380,1830},{375,495,1665,770},{1420,765,1665,1770},{465,1565,1425,1770},{465,870,665,1570},{660,870,1340,1055},{1245,1050,1340,1520},{715,1415,1250,1520},{715,1115,805,1420},{800,1115,1205,1195},{1130,1190,1205,1390},{860,1325,1135,1390},{860,1220,920,1330},{915,1220,1100,1290} };auto res = Solution().Ans(a);AssertEx(29340, res);}

扩展阅读

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

视频课程

先学简单的课程,请移步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++**实现。

相关文章:

  • 基于大模型预测亚急性脊髓联合变性的综合技术方案研究报告大纲
  • 【MySQL】第9节|Innodb底层原理与Mysql日志机制深入剖析(二)
  • Jetpack Compose 中更新应用语言
  • 【MySQL】第10节|MySQL全局优化与Mysql 8.0新增特性详解
  • Docker核心技术:Docker原理之Union文件系统
  • Google 推出 Flow —— AI 电影制作新平台
  • 在vue中重复组件导入简化方案
  • 【Leetcode 每日一题】2131. 连接两字母单词得到的最长回文串
  • 校园二手交易系统
  • Java详解LeetCode 热题 100(21):LeetCode 240. 搜索二维矩阵 II(Search a 2D Matrix II)详解
  • 【计算机网络】基于TCP进行socket编程——实现客户端到服务端远程命令行操作
  • DAY36
  • 【MySQL】第十一弹——JDBC编程
  • PHP学习笔记(十)
  • 特征预处理
  • QML混合编程图像刷新策略
  • JavaSE核心知识点03高级特性
  • JavaScript数据类型及内置函数详解目录
  • 【MYSQL】索引、存储引擎
  • Next.js V15 在异步页面中,获取路由动态参数的几个坑
  • 网站被抄袭怎么办/下载百度 安装
  • 毕业设计做网站/企业关键词排名优化网址
  • 做网站-信科网络/注册推广赚钱一个10元
  • wordpress rss格式/手机seo排名
  • 百度网站链接提交入口/企业自建网站
  • 深圳有做公司网站/建站优化