本文涉及知识点
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
C++DFS
C++并集查找
P3127 USACO15OPEN Trapped in the Haybales G
题目描述
Farmer John 收到了一批 N N N 个大型干草捆( 1 ≤ N ≤ 100 , 000 1 \le N \le 100,000 1≤N≤100,000),并将它们放置在他通往谷仓的道路上的不同位置。不幸的是,他完全忘记了奶牛 Bessie 正在这条路上吃草,她现在可能被困在这些干草捆之间了!每个干草捆 j j j 有一个大小 S j S_j Sj 和一个位置 P j P_j Pj,表示它在这条一维道路上的位置。Bessie 可以在道路上自由移动,甚至可以移动到干草捆所在的位置,但她无法穿过这个位置。唯一的例外是,如果她朝同一方向连续移动 D D D 单位的距离,她将获得足够的速度,能够突破并永久消除任何大小严格小于 D D D 的干草捆。当然,在突破之后,她可能会打开更多的空间,从而有机会突破其他干草捆,并继续消除它们。
如果 Bessie 最终能够突破最左侧或最右侧的干草捆,她就可以成功逃脱。请计算道路中所有无法逃脱的实数起始位置的总面积。
输入格式
输入的第一行包含 N N N。接下来的 N N N 行描述每个干草捆,每行包含两个整数,分别表示干草捆的大小和位置,范围均为 1 ... 1 0 9 1 \ldots 10^9 1...109。所有位置均不相同。
输出格式
输出一个整数,表示 Bessie 无法逃脱的道路总面积。
输入输出样例 #1
输入 #1
5
8 1
1 4
8 8
7 15
4 20
输出 #1
14
BFS( 错误 错误 错误)
性质一 :n堆干草,将整个道路分成n-1区间。任何区间,只要有一个点能够突破所有干草堆,则整个区间能突破所有干草堆。以向右突破为例,本区间所有点先向左到左端点后,再向右。
0 ≤ i ≤ N − 2 0\leq i \le N-2 0≤i≤N−2 ri记录到达第i堆干草至少已经向右ri,才能向右突破所有草堆。
ri =max(ri+1-此区间长度,si-此区间长度)
如果ri<0,可以做起点。
类似:lefti记录到达第i+1堆干草,至少已经向左lefti,才能向左突破所有草堆。
如果区间lefti<0或ri<0,则称此区间能够直接突破。如果没有区间能直接突破,则所有区间不能突破。
性质二 :区间一和区间二相邻,如果区间一为起点能够到达区间二,则区间二能够突破所有干草,则区间一也能。即一个连通区域只要有一个点能突破,则连通区域所有点都能突破。注意 :相邻是有向的。
实现:边反向后,以直接突破的区间为起点,BFS。
错误原因 :存在以下情况: 区间一 → 区间二 → 区间三 区间一\rightarrow 区间二 \rightarrow 区间三 区间一→区间二→区间三,但区间二无法到达区间三。
暴力解法:枚举所有边而不是相邻边。时间复杂度:O(nn) 时间超限。
BFS+虚拟节点+前缀和(错误)
根据上述分析,我们可以得出如下结论。
性质一 :一个区间可以缩成一个点,共n-1个点,增加一个虚拟节点n-1,共n个节点。
性质二 :所有能直接突破的区间对应的点连向n-1。
如果是BFS,不需要虚拟节点。
增加区间间的边
以向右的边( i < j < k i<j<k i<j<k)为例。从i能够到达k,且不能到达k+1。向左的边类似。
性质三 :如果j能够到达k,则只需要 i → j → k i \rightarrow j \rightarrow k i→j→k,无需 i → k i \rightarrow k i→k
性质四 :如果i不能到达j,则i也无法到达k。
小结一 :如果 j → k j \rightarrow k j→k,则除j外,不需要任意其它边连向k。
性质五 :如果i不能到达 k + 1 k+1 k+1,则一定不能到达 k + 2 k+2 k+2。
栈sta记录所有 ∀ j \forall j ∀j都不能到达的k,从栈底到栈顶降序。当i能到达栈顶,连边出栈。
判断i能否到达栈顶:
区间i到栈顶的距离 是否大于 栈顶S。
错误原因 :

区间一无法到达区间二,区间二无法直接到达区间三,但 区间 2 → 区间 1 → 区间 3 区间2 \rightarrow 区间1 \rightarrow 区间3 区间2→区间1→区间3却成立。
因为干草堆是永远消失。
错误代码
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<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 _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::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 // DEBUG
auto res = Solution().Ans(sp);
cout << res << "\n";
return 0;
}
并集查找
令某区间i,不转向向左能到left,向右能到r。此时left到r之间的干草堆已经被消除,故可以看成一个大区间。
left,r不转向最左能够到达left1,最右能够到达r1。如果 l e f t = = l e f t 1 且 r = = r 1 left == left1 且 r == r1 left==left1且r==r1则迭代结束。
否则 l e f t = l e f t 1 , r = r 1 left=left1,r=r1 left=left1,r=r1继续迭代。
如果最终 l e f t < 0 或 r > N − 2 left < 0或r >N-2 left<0或r>N−2则区间i能够突破所有。
性质一 :如果i不能突破,则i,r都不能突破。区间i到达区间k 速度 ≥ 0 速度\ge0 速度≥0,从区间k出发速度0。
性质二:同理i能到达j,j能到达的区间,则i也能到达。leftj记录j能到达的最左点,如果i能到达j,则能到达leftj ,leftleft\[j],我们用并集查找记录。
超时代码
c
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都无法突破,flagi+1...r= true,如果flagi之间 ans += vLeni并忽略。
除r++ 外的时间复杂度接近:O ( N ) (N) (N)
r++的时间复杂度估计时:O(N)
代码
核心代码
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<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 _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::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 // DEBUG
auto res = Solution().Ans(sp);
cout << res << "\n";
return 0;
}
单元测试
cpp
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);
}
疑问
感觉下面代码有问题:
cpp
for (; x < r; x++)
{
uf.Connect(b, b, x, left);
flag[x] = true;
}
改成这样也能通过,且容易理解。
cpp
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++**实现。
