【排序 离散化 二维前缀和】 P7149 [USACO20DEC] Rectangular Pasture S|普及+

本文涉及知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

离散化

P7149 USACO20DEC Rectangular Pasture S

题目描述

Farmer John 最大的牧草地可以被看作是一个由方格组成的巨大的二维方阵(想象一个巨大的棋盘)。现在,有 N N N 头奶牛正占据某些方格( 1 ≤ N ≤ 2500 1≤N≤2500 1≤N≤2500)。

Farmer John 想要建造一个可以包围一块矩形区域的栅栏;这个矩形必须四边与 x x x 轴和 y y y 轴平行,最少包含一个方格。请帮助他求出他可以包围在这样的区域内的不同的奶牛子集的数量。注意空集应当被计算为答案之一。

输入格式

输入的第一行包含一个整数 N N N。以下 N N N 行每行包含两个空格分隔的整数,表示一头奶牛所在方格的坐标 ( x , y ) (x,y) (x,y)。所有 x x x 坐标各不相同,所有 y y y 坐标各不相同。所有 x x x 与 y y y 的值均在 0 ... 10 9 0...10^9 0...109 范围内。

输出格式

输出 FJ 可以包围的奶牛的子集数量。可以证明这个数量可以用 64 位有符号整数型存储(例如 C/C++ 中的long long)。

输入输出样例 #1

输入 #1

复制代码
4
0 2
1 0
2 3
3 5

输出 #1

复制代码
13

说明/提示

共有 2 4 2^4 24 个子集。FJ 不能建造一个栅栏仅包围奶牛 1 1 1、 2 2 2、 4 4 4,或仅包围奶牛 2 2 2、 4 4 4,或仅包围奶牛 1 1 1、 4 4 4,所以答案为 2 4 − 3 = 16 − 3 = 13 2^4-3=16-3=13 24−3=16−3=13。

  • 测试点 2-3 满足 N ≤ 20 N≤20 N≤20。
  • 测试点 4-6 满足 N ≤ 100 N≤100 N≤100。
  • 测试点 7-12 满足 N ≤ 500 N≤500 N≤500。
  • 测试点 13-20 没有额外限制。

供题:Benjamin Qi

排序 分类讨论 树状数组 离散化

注意 :所有 x x x 坐标各不相同,所有 y y y 坐标各不相同。

按x排序,对y离散化,方便用树状数组结论left+1,r-1直接的y。

令最左、最右的奶牛纵坐标是left,r。

分类一:空矩形,只有一种情况。没有任何奶牛。

分类二:left=r,包围一头奶牛。共有N种情况。

分类三:left < r。令这两头奶牛的纵坐标为y1,y2,如果y1大于j2,交换之。x在left+1,r-1,y小于等于y1的奶牛数量为z1,y大于等于y2奶牛的数量是z2。则ans +=z1*z2。
时间复杂度:O(nnlogn)

错误解法:离散化+二维前缀和

离散化后,二维前缀和。枚举左上角,右下角。如果最左(右上下)行(列)没有奶牛删除之。故矩形的左上、右下一定有奶牛。

左边缘一定有牛,上边缘一定有牛,但左上不一定有牛。

优化

x,y离散化后,用二维前缀和查询:mat0...y1left...r和maty2...R-1的奶牛数。
时间复杂度:O(nn)

代码

核心代码

cpp 复制代码
#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;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	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<class T = int>
class CDiscretize //离散化
{
public:
	CDiscretize(vector<T> nums)
	{
		sort(nums.begin(), nums.end());
		nums.erase(std::unique(nums.begin(), nums.end()), nums.end());
		m_nums = nums;
		for (int i = 0; i < nums.size(); i++)
		{
			m_mValueToIndex[nums[i]] = i;
		}
	}
	int operator[](const T value)const
	{
		auto it = m_mValueToIndex.find(value);
		if (m_mValueToIndex.end() == it)
		{
			return -1;
		}
		return it->second;
	}
	int size()const
	{
		return m_mValueToIndex.size();
	}
	vector<T> m_nums;
protected:
	unordered_map<T, int> m_mValueToIndex;
};
template<class T = int>
class CPreSum2 {
public:
	template<class _Pr>
	CPreSum2(int rowCnt, int colCount, _Pr pr) :m_iRowCnt(rowCnt), m_iColCnt(colCount) {
		m_vSum.assign(rowCnt + 1, vector<int>(colCount + 1));
		for (int r = 0; r < rowCnt; r++) {
			for (int c = 0; c < colCount; c++) {
				m_vSum[r + 1][c + 1] = m_vSum[r][c + 1] + m_vSum[r + 1][c] - m_vSum[r][c] + pr(r, c);
			}
		}
	}
	T Get(int left, int top, int right, int bottom)const {
		return m_vSum[bottom + 1][right + 1] - m_vSum[top][right + 1] - m_vSum[bottom + 1][left] + m_vSum[top][left];
	}
	T GetTopLeft(int bottom, int right) { return m_vSum[bottom + 1][right + 1]; }
	T GetBottomRight(int top, int left) { return Get(left, top, m_iColCnt - 1, m_iRowCnt - 1); }
	vector<vector<T>> m_vSum;
	const int m_iRowCnt, m_iColCnt;
};

class Solution {
public:
	long long Ans(vector<pair<int, int>>& pts) {
		const int N = pts.size();
		vector<int> xs, ys;
		for (const auto& [x, y] : pts) {
			xs.emplace_back(x); ys.emplace_back(y);
		}
		CDiscretize<int> disx(xs), disy(ys);
		const int R = disy.size(), C = disx.size();
		vector<vector<int>> mat(R, vector<int>(C));
		for (auto& [x, y] : pts) {
			x = disx[x]; y = disy[y];
			mat[y][x]++;
		}
		CPreSum2<> preSum(R, C, [&](int r, int c) {return mat[r][c]; });
		sort(pts.begin(), pts.end());
		long long ans = N + 1;
		for (int i = 0; i < N; i++) {
			for (int j = i + 1; j < N; j++) {
				const int y1 = min(pts[i].second, pts[j].second);
				const int y2 = max(pts[i].second, pts[j].second);
				const long long z1 = preSum.Get(pts[i].first, 0, pts[j].first, y1);
				const long long z2 = preSum.Get(pts[i].first, y2, pts[j].first, R - 1);
				ans += z1 * z2;
			}
		}
		return ans;
	}
};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	auto pts = Read<pair<int, int>>();
#ifdef _DEBUG		
	//printf("R=%d,C=%d",R,C);
	Out(pts, ",pts=");
	//Out(a, ",a=");
	//Out(que, ",que=");
	/*Out(que, "que=");*/
#endif // DEBUG		
	auto res = Solution().Ans(pts);
	cout << res;
	return 0;
}

单元测试

cpp 复制代码
	vector<pair<int, int>> pts;
		TEST_METHOD(TestMethod1)
		{
			pts = { {0,2},{1,0},{2,3},{3,5} };
			auto res = Solution().Ans(pts);
			AssertEx(13LL, 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++**实现。

相关推荐
wabs66615 小时前
关于贪心算法的一些自我总结【力扣45.跳跃游戏II】【灵感来源:代码随想录】
算法·贪心算法·复盘
2401_8769641316 小时前
【湖北专升本】2026湖北专升本真题PDF+备考资料汇总
数据结构·人工智能·经验分享·深度学习·算法·计算机视觉
basketball61616 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
嗝o゚16 小时前
CANN GE 算子融合——融合算法与调度策略
算法·昇腾·cann·ge
小江的记录本16 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
Fre丸子_17 小时前
自定义文件夹选取功能
c++
Ulyanov18 小时前
用声明式语法重新定义Python桌面UI:QML+PySide6现代开发入门(一)
开发语言·python·算法·ui·系统仿真·雷达电子对抗仿真
数据科学小丫18 小时前
特征工程处理
人工智能·算法·机器学习
z落落18 小时前
C#参数区别
java·算法·c#
思麟呀19 小时前
C++工业级日志项目(六)异步日志器
linux·c++·windows