【前缀和 BFS 并集查找】P3127 [USACO15OPEN] Trapped in the Haybales G|省选-
本文涉及知识点
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
C++DFS
C++并集查找
P3127 [USACO15OPEN] Trapped in the Haybales G
题目描述
Farmer John 收到了一批 NNN 个大型干草捆(1≤N≤100,0001 \le N \le 100,0001≤N≤100,000),并将它们放置在他通往谷仓的道路上的不同位置。不幸的是,他完全忘记了奶牛 Bessie 正在这条路上吃草,她现在可能被困在这些干草捆之间了!每个干草捆 jjj 有一个大小 SjS_jSj 和一个位置 PjP_jPj,表示它在这条一维道路上的位置。Bessie 可以在道路上自由移动,甚至可以移动到干草捆所在的位置,但她无法穿过这个位置。唯一的例外是,如果她朝同一方向连续移动 DDD 单位的距离,她将获得足够的速度,能够突破并永久消除任何大小严格小于 DDD 的干草捆。当然,在突破之后,她可能会打开更多的空间,从而有机会突破其他干草捆,并继续消除它们。
如果 Bessie 最终能够突破最左侧或最右侧的干草捆,她就可以成功逃脱。请计算道路中所有无法逃脱的实数起始位置的总面积。
输入格式
输入的第一行包含 NNN。接下来的 NNN 行描述每个干草捆,每行包含两个整数,分别表示干草捆的大小和位置,范围均为 1…1091 \ldots 10^91…109。所有位置均不相同。
输出格式
输出一个整数,表示 Bessie 无法逃脱的道路总面积。
输入输出样例 #1
输入 #1
5
8 1
1 4
8 8
7 15
4 20
输出 #1
14
BFS(错误错误错误)
性质一:n堆干草,将整个道路分成n-1区间。任何区间,只要有一个点能够突破所有干草堆,则整个区间能突破所有干草堆。以向右突破为例,本区间所有点先向左到左端点后,再向右。
0≤i≤N−20\leq i \le N-20≤i≤N−2 r[i]记录到达第i堆干草至少已经向右r[i],才能向右突破所有草堆。
r[i] =max(r[i+1]-此区间长度,s[i]-此区间长度)
如果r[i]<0,可以做起点。
类似:left[i]记录到达第i+1堆干草,至少已经向左left[i],才能向左突破所有草堆。
如果区间left[i]<0或r[i]<0,则称此区间能够直接突破。如果没有区间能直接突破,则所有区间不能突破。
性质二:区间一和区间二相邻,如果区间一为起点能够到达区间二,则区间二能够突破所有干草,则区间一也能。即一个连通区域只要有一个点能突破,则连通区域所有点都能突破。注意:相邻是有向的。
实现:边反向后,以直接突破的区间为起点,BFS。
错误原因:存在以下情况:区间一→区间二→区间三区间一\rightarrow 区间二 \rightarrow 区间三区间一→区间二→区间三,但区间二无法到达区间三。
暴力解法:枚举所有边而不是相邻边。时间复杂度:O(nn) 时间超限。
BFS+虚拟节点+前缀和(错误)
根据上述分析,我们可以得出如下结论。
性质一:一个区间可以缩成一个点,共n-1个点,增加一个虚拟节点n-1,共n个节点。
性质二:所有能直接突破的区间对应的点连向n-1。
如果是BFS,不需要虚拟节点。
增加区间间的边
以向右的边(i<j<ki<j<ki<j<k)为例。从i能够到达k,且不能到达k+1。向左的边类似。
性质三:如果j能够到达k,则只需要i→j→ki \rightarrow j \rightarrow ki→j→k,无需i→ki \rightarrow ki→k
性质四:如果i不能到达j,则i也无法到达k。
小结一:如果j→kj \rightarrow kj→k,则除j外,不需要任意其它边连向k。
性质五:如果i不能到达k+1k+1k+1,则一定不能到达k+2k+2k+2。
栈sta记录所有∀j\forall j∀j都不能到达的k,从栈底到栈顶降序。当i能到达栈顶,连边出栈。
判断i能否到达栈顶:
区间i到栈顶的距离 是否大于 栈顶S。
错误原因:
区间一无法到达区间二,区间二无法直接到达区间三,但区间2→区间1→区间3区间2 \rightarrow 区间1 \rightarrow 区间3区间2→区间1→区间3却成立。
因为干草堆是永远消失。
错误代码
#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<int N = 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;AuotToFile();}void writestr(const char* sz) {strcpy(m_p, sz);m_p += strlen(sz);AuotToFile();}inline void write(char ch){*m_p++ = ch;AuotToFile();}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);m_p = puffer;}~COutBuff() {ToFile();}
private:inline void AuotToFile() {if (m_p - puffer > N - 100) {ToFile();}}char puffer[N], * m_p;
};template<int N = 1'000'000>
class CInBuff
{
public:inline CInBuff() {}inline CInBuff<N>& operator>>(char& ch) {FileToBuf();ch = *S++;return *this;}inline CInBuff<N>& operator>>(int& val) {FileToBuf();int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行 return *this;}inline CInBuff& operator>>(long long& val) {FileToBuf();long long x(0); int f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行return *this;}template<class T1, class T2>inline CInBuff& operator>>(pair<T1, T2>& val) {*this >> val.first >> val.second;return *this;}template<class T1, class T2, class T3>inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val);return *this;}template<class T1, class T2, class T3, class T4>inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);return *this;}template<class T = int>inline CInBuff& operator>>(vector<T>& val) {int n;*this >> n;val.resize(n);for (int i = 0; i < n; i++) {*this >> val[i];}return *this;}template<class T = int>vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {*this >> ret[i];}return ret;}template<class T = int>vector<T> Read() {vector<T> ret;*this >> ret;return ret;}
private:inline void FileToBuf() {const int canRead = m_iWritePos - (S - buffer);if (canRead >= 100) { return; }if (m_bFinish) { return; }for (int i = 0; i < canRead; i++){buffer[i] = S[i];//memcpy出错 }m_iWritePos = canRead;buffer[m_iWritePos] = 0;S = buffer;int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);if (readCnt <= 0) { m_bFinish = true; return; }m_iWritePos += readCnt;buffer[m_iWritePos] = 0;S = buffer;}int m_iWritePos = 0; bool m_bFinish = false;char buffer[N + 10], * S = buffer;
};class Solution {public:long long Ans(vector<pair<int, int>>& sp) { const int N = sp.size();sort(sp.begin(), sp.end(), [&](const auto& p1, const auto& p2) {return p1.second < p2.second; });vector<long long >left(N ), r(N ),vLen(N);for (int i = N - 2; i >= 0; i--) {vLen[i] = sp[i + 1].second - sp[i].second;r[i] = max((long long)sp[i + 1].first, r[i + 1] ) - vLen[i];}vector<long long> preSum = { 0 };for (const auto& i : vLen) {preSum.emplace_back(preSum.back() + i);}long long ans = 0;queue<int> que;vector<bool> vis(N - 1);auto Add = [&](int cur) {if (vis[cur]) { return; }vis[cur] = true;que.emplace(cur);ans -= vLen[cur];};for (int i = 0; i + 1 < N; i++) { if (0 == i) {left[i] = sp[i].first - vLen[i];}else {left[i] = max( (long long)sp[i].first,left[i-1]) - vLen[i];}if ((left[i] < 0) || (r[i] < 0)) { Add(i); }}vector<vector<int>> neiBoBack(N - 1); {//处理向右的边stack<int> sta;for (int i = N - 2; i >= 0; i--) {while (sta.size()&&( preSum[sta.top()]- preSum[i] > sp[sta.top()].first)){neiBoBack[sta.top()].emplace_back(i);sta.pop();}sta.emplace(i);}}{//处理向左的边stack<int> sta;for (int i = 0; i + 1 < N; i++) {while(sta.size()&&(preSum[i+1]-preSum[sta.top()+1] > sp[sta.top()+1].first)){neiBoBack[sta.top()].emplace_back(i);sta.pop();}sta.emplace(i);}}while (que.size()) {const auto cur = que.front(); que.pop();for (const auto&next : neiBoBack[cur]) {Add(next);}}return ans+ preSum.back();}};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUGios::sync_with_stdio(0); cin.tie(nullptr); cout.tie(nullptr);auto sp = Read<pair<int, int>>();
#ifdef _DEBUG//printf("N=%d", N);Out(sp, ",sp=");//Out(que, ",=que");
#endif // DEBUGauto res = Solution().Ans(sp);cout << res << "\n";return 0;
}
并集查找
令某区间i,不转向向左能到left,向右能到r。此时left到r之间的干草堆已经被消除,故可以看成一个大区间。
[left,r]不转向最左能够到达left1,最右能够到达r1。如果left==left1且r==r1left == left1 且 r == r1left==left1且r==r1则迭代结束。
否则left=left1,r=r1left=left1,r=r1left=left1,r=r1继续迭代。
如果最终left<0或r>N−2left < 0或r >N-2left<0或r>N−2则区间i能够突破所有。
性质一:如果i不能突破,则[i,r]都不能突破。区间i到达区间k速度≥0速度\ge0速度≥0,从区间k出发速度0。
性质二:同理i能到达j,j能到达的区间,则i也能到达。left[j]记录j能到达的最左点,如果i能到达j,则能到达left[j] ,left[left[j]],我们用并集查找记录。
超时代码
class Solution {public:long long Ans(vector<pair<int, int>>& sp) {const int N = sp.size();sort(sp.begin(), sp.end(), [&](const auto& p1, const auto& p2) {return p1.second < p2.second; });vector<long long > vLen(N);for (int i = N - 2; i >= 0; i--) {vLen[i] = sp[i + 1].second - sp[i].second;}vector<long long> preSum = { 0 };for (const auto& i : vLen) {preSum.emplace_back(preSum.back() + i);}CUnionFindDirect uf(N);bool b = false;auto Check = [&](int x) {int left = x, r = x + 1;while (left >= 0){const long long len = preSum[r] - preSum[left];if (len > sp[left].first) {//可以向左if (0 == left) { break; }left = uf.GetMaxDest(left - 1);if (left == N - 1) { break; }}else if (len > sp[r].first) {//可以向右r++;if (r == N) { break; }}else {uf.Connect(b, b, x, left);return false;}}uf.Connect(b, b, x, N - 1);return true;};long long ans = 0;for (int i = 0; i + 1 < N; i++) {Check(i);if (N - 1 != uf.GetMaxDest(i)) {ans += vLen[i];}}return ans;}};
解决方法
如果区间i无法突破,则[i+1…r]都无法突破,flag[i+1…r]= true,如果flag[i]之间 ans += vLen[i]并忽略。
除r++ 外的时间复杂度接近:O(N)(N)(N)
r++的时间复杂度估计时:O(N)
代码
核心代码
#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<int N = 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;AuotToFile();}void writestr(const char* sz) {strcpy(m_p, sz);m_p += strlen(sz);AuotToFile();}inline void write(char ch){*m_p++ = ch;AuotToFile();}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);m_p = puffer;}~COutBuff() {ToFile();}
private:inline void AuotToFile() {if (m_p - puffer > N - 100) {ToFile();}}char puffer[N], * m_p;
};template<int N = 1'000'000>
class CInBuff
{
public:inline CInBuff() {}inline CInBuff<N>& operator>>(char& ch) {FileToBuf();ch = *S++;return *this;}inline CInBuff<N>& operator>>(int& val) {FileToBuf();int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行 return *this;}inline CInBuff& operator>>(long long& val) {FileToBuf();long long x(0); int f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行return *this;}template<class T1, class T2>inline CInBuff& operator>>(pair<T1, T2>& val) {*this >> val.first >> val.second;return *this;}template<class T1, class T2, class T3>inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val);return *this;}template<class T1, class T2, class T3, class T4>inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);return *this;}template<class T = int>inline CInBuff& operator>>(vector<T>& val) {int n;*this >> n;val.resize(n);for (int i = 0; i < n; i++) {*this >> val[i];}return *this;}template<class T = int>vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {*this >> ret[i];}return ret;}template<class T = int>vector<T> Read() {vector<T> ret;*this >> ret;return ret;}
private:inline void FileToBuf() {const int canRead = m_iWritePos - (S - buffer);if (canRead >= 100) { return; }if (m_bFinish) { return; }for (int i = 0; i < canRead; i++){buffer[i] = S[i];//memcpy出错 }m_iWritePos = canRead;buffer[m_iWritePos] = 0;S = buffer;int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);if (readCnt <= 0) { m_bFinish = true; return; }m_iWritePos += readCnt;buffer[m_iWritePos] = 0;S = buffer;}int m_iWritePos = 0; bool m_bFinish = false;char buffer[N + 10], * S = buffer;
};class CUnionFindDirect
{
public:CUnionFindDirect(int iSize){m_vRoot.resize(iSize);iota(m_vRoot.begin(), m_vRoot.end(), 0);}void Connect(bool& bConflic, bool& bCyc, int iFrom, int iTo){bConflic = bCyc = false;if (iFrom != m_vRoot[iFrom]){bConflic = true;}Fresh(iTo);if (m_vRoot[iTo] == iFrom){bCyc = true;}if (bConflic || bCyc){return;}m_vRoot[iFrom] = m_vRoot[iTo];}int GetMaxDest(int iFrom){Fresh(iFrom);return m_vRoot[iFrom];}
private:int Fresh(int iNode){if (m_vRoot[iNode] == iNode){return iNode;}return m_vRoot[iNode] = Fresh(m_vRoot[iNode]);}vector<int> m_vRoot;
};
class Solution {
public:long long Ans(vector<pair<int, int>>& sp) {const int N = sp.size();sort(sp.begin(), sp.end(), [&](const auto& p1, const auto& p2) {return p1.second < p2.second; });vector<long long > vLen(N);for (int i = N - 2; i >= 0; i--) {vLen[i] = sp[i + 1].second - sp[i].second;}vector<long long> preSum = { 0 };for (const auto& i : vLen) {preSum.emplace_back(preSum.back() + i);}CUnionFindDirect uf(N);bool b = false;vector<bool> flag(N);auto Check = [&](int x) {int left = x, r = x + 1;while (left >= 0){const long long len = preSum[r] - preSum[left];if (len > sp[left].first) {//可以向左if (0 == left) { break; }left = uf.GetMaxDest(left - 1);if (left == N - 1) { break; }}else if (len > sp[r].first) {//可以向右r++;if (r == N) { break; }}else {for (; x < r; x++){uf.Connect(b, b, x, left);flag[x] = true;}return false;}}uf.Connect(b, b, x, N - 1);return true;};long long ans = 0;for (int i = 0; i + 1 < N; i++) {if (flag[i]) {ans += vLen[i];continue;}Check(i);if (N - 1 != uf.GetMaxDest(i)) {ans += vLen[i];}}return ans;}
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUGios::sync_with_stdio(0); cin.tie(nullptr); cout.tie(nullptr);auto sp = Read<pair<int, int>>();
#ifdef _DEBUG//printf("N=%d", N);Out(sp, ",sp=");//Out(que, ",=que");
#endif // DEBUGauto res = Solution().Ans(sp);cout << res << "\n";return 0;
}
单元测试
vector<pair<int, int>> sp;TEST_METHOD(TestMethod11){sp = { {8,1},{1,4},{8,8},{7,15},{4,20} };auto res = Solution().Ans(sp);AssertEx(14LL, res);}TEST_METHOD(TestMethod12){sp = { {1000,1},{4,101},{3,103 },{1,105} };//区间长度100,[1000,4] 2 [4,3] 2[3,1]auto res = Solution().Ans(sp);AssertEx(2LL, res);}
疑问
感觉下面代码有问题:
for (; x < r; x++){uf.Connect(b, b, x, left);flag[x] = true;}
改成这样也能通过,且容易理解。
uf.Connect(b, b, x, left);for (; x < r; x++){ flag[x] = true;}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步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++**实现。