本文涉及知识点
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离散化后,用二维前缀和查询:mat[0...y1][left...r]和mat[y2...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++**实现。