【扫描线 线段树】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 1≤N<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++**实现。