矩形并的面积,线段树,扫描线算法模板

using namespace std;
#define int long long
using ll = long long;
template<typename T = long long>
struct RectUnion {
	struct Event {
		T x, y1, y2;
		int mark;
		bool operator<(const Event& o) const { return x < o.x; }
	};
	
	struct SegTree {
		int n;
		vector<T> ys;
		vector<int> cnt;
		vector<T> len;
		
		SegTree(vector<T> coords) {
			sort(coords.begin(), coords.end());
			coords.erase(unique(coords.begin(), coords.end()), coords.end());
			ys = coords;
			n = (int)ys.size() - 1;
			if (n < 1) n = 1;
			cnt.resize(n << 2);
			len.resize(n << 2);
		}
		
		inline T segLen(int l, int r) {  // 第l个区间到第r个区间的总长度
			return ys[r + 1] - ys[l];
		}
		
		void pull(int p, int l, int r) {
			if (cnt[p]) len[p] = segLen(l, r);
			else if (l == r) len[p] = 0;
			else len[p] = len[p<<1] + len[p<<1|1];
		}
		
		void upd(int p, int l, int r, int L, int R, int v) {
			if (L > r || R < l) return;
			if (L <= l && r <= R) {
				cnt[p] += v;
				pull(p, l, r);
				return;
			}
			int mid = (l + r) >> 1;
			upd(p<<1, l, mid, L, R, v);
			upd(p<<1|1, mid+1, r, L, R, v);
			pull(p, l, r);
		}
		
		// 对外接口:在[y1,y2]闭区间更新(内部自动转开区间处理)
		void update(T y1, T y2, int v) {
			int L = lower_bound(ys.begin(), ys.end(), y1) - ys.begin();
			int R = lower_bound(ys.begin(), ys.end(), y2) - ys.begin();
			// 转成开区间下标:闭区间[y1,y2]对应开区间[y1, y2+ε)
			// 在离散化数组中就是找y2的下一个位置
			// 但这里我们直接用:闭区间[l,r]对应线段树区间[l, r-1](因为ys[r]是右端点)
			// 实际上:y2对应下标pos,则区间[pos, next) 要包含y2这个点,需要到pos
			// 简化:用户传闭区间,我们内部转 [y1, y2+1) 如果坐标是整数且单位长度
			// 通用做法:找到y2的位置,然后-1作为右边界(因为ys[i]到ys[i+1]是一个区间)
			R--;  // 关键:y2是右端点,对应线段树区间要-1
			if (L <= R) upd(1, 0, n-1, L, R, v);
		}
		
		T query() { return len[1]; }
	};
	
	vector<Event> e;
	bool built = false;
	
	// 用户友好接口:添加闭区间矩形 [x1,x2] × [y1,y2]
	void add(T x1, T y1, T x2, T y2) {
		// 内部自动转开区间:[x1, x2+1) × [y1, y2+1)
		// 适用于整数坐标,单位格子的情况
		e.push_back({x1, y1, y2 + 1, 1});
		e.push_back({x2 + 1, y1, y2 + 1, -1});
		built = false;
	}
	
	// 如果用户明确要开区间,提供raw接口
	void addRaw(T x1, T y1, T x2, T y2) {
		e.push_back({x1, y1, y2, 1});
		e.push_back({x2, y1, y2, -1});
		built = false;
	}
	
	T solve() {
		if (e.empty()) return 0;
		
		// 收集y坐标(已经是开区间的y2了)
		vector<T> ys;
		for (auto& ev : e) {
			ys.push_back(ev.y1);
			ys.push_back(ev.y2);
		}
		
		SegTree st(ys);
		sort(e.begin(), e.end());
		
		T res = 0, lastX = e[0].x;
		
		for (size_t i = 0; i < e.size(); ) {
			T x = e[i].x;
			T dx = x - lastX;
			if (dx) res += dx * st.query();
			
			while (i < e.size() && e[i].x == x) {
				// 注意:这里的y1,y2已经是内部开区间坐标了
				int L = lower_bound(st.ys.begin(), st.ys.end(), e[i].y1) - st.ys.begin();
				int R = lower_bound(st.ys.begin(), st.ys.end(), e[i].y2) - st.ys.begin() - 1;
				if (L <= R) st.upd(1, 0, st.n-1, L, R, e[i].mark);
				i++;
			}
			lastX = x;
		}
		return res;
	}
};

// ==================== 使用 ====================

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	long long H, W, h, w;
	int n;
	cin >> H >> W >> h >> w >> n;
	
	RectUnion<ll> ru;
	
	for (int i = 0; i < n; i++) {
		long long r, c; 
		cin >> r >> c;
		long long x1 = max(1LL, r - h + 1);
		long long y1 = max(1LL, c - w + 1);
		long long x2 = min(H - h + 1, r);
		long long y2 = min(W - w + 1, c);
		
		// 直接传闭区间,模板内部处理转换
		if (x1 <= x2 && y1 <= y2) ru.add(x1, y1, x2, y2);
      //左下- 右上
	}
	
	long long total = (H - h + 1) * (W - w + 1);
	long long bad = ru.solve();
	cout << total - bad << '\n';
	
	return 0;
}```
posted @ 2026-04-10 16:45  majikko  阅读(6)  评论(0)    收藏  举报