【前缀和 BFS 并集查找】P3127 [USACO15OPEN] Trapped in the Haybales G|省选-

本文涉及知识点

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 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 < 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也能到达。left\[j\]记录j能到达的最左点,如果i能到达j,则能到达left\[j\] ,left\[left\[j\]\],我们用并集查找记录。 ### 超时代码 ```c class Solution { public: long long Ans(vector>& sp) { const int N = sp.size(); sort(sp.begin(), sp.end(), [&](const auto& p1, const auto& p2) {return p1.second < p2.second; }); vector vLen(N); for (int i = N - 2; i >= 0; i--) { vLen[i] = sp[i + 1].second - sp[i].second; } vector 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) ## 代码 ### 核心代码 ```cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; template std::istream& operator >> (std::istream& in, pair& pr) { in >> pr.first >> pr.second; return in; } template std::istream& operator >> (std::istream& in, tuple& t) { in >> get<0>(t) >> get<1>(t) >> get<2>(t); return in; } template std::istream& operator >> (std::istream& in, tuple& t) { in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t); return in; } template vector Read() { int n; cin >> n; vector ret(n); for (int i = 0; i < n; i++) { cin >> ret[i]; } return ret; } template vector ReadNotNum() { vector ret; T tmp; while (cin >> tmp) { ret.emplace_back(tmp); if ('\n' == cin.get()) { break; } } return ret; } template vector Read(int n) { vector ret(n); for (int i = 0; i < n; i++) { cin >> ret[i]; } return ret; } template class COutBuff { public: COutBuff() { m_p = puffer; } template 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 class CInBuff { public: inline CInBuff() {} inline CInBuff& operator>>(char& ch) { FileToBuf(); ch = *S++; return *this; } inline CInBuff& 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 inline CInBuff& operator>>(pair& val) { *this >> val.first >> val.second; return *this; } template inline CInBuff& operator>>(tuple& val) { *this >> get<0>(val) >> get<1>(val) >> get<2>(val); return *this; } template inline CInBuff& operator>>(tuple& val) { *this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val); return *this; } template inline CInBuff& operator>>(vector& val) { int n; *this >> n; val.resize(n); for (int i = 0; i < n; i++) { *this >> val[i]; } return *this; } template vector Read(int n) { vector ret(n); for (int i = 0; i < n; i++) { *this >> ret[i]; } return ret; } template vector Read() { vector 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 m_vRoot; }; class Solution { public: long long Ans(vector>& sp) { const int N = sp.size(); sort(sp.begin(), sp.end(), [&](const auto& p1, const auto& p2) {return p1.second < p2.second; }); vector vLen(N); for (int i = N - 2; i >= 0; i--) { vLen[i] = sp[i + 1].second - sp[i].second; } vector preSum = { 0 }; for (const auto& i : vLen) { preSum.emplace_back(preSum.back() + i); } CUnionFindDirect uf(N); bool b = false; vector 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>(); #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> 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; } ``` ![](https://img-blog.csdnimg.cn/8d37dcd13ddb4df9af8f95fefd86828d.gif) ## 扩展阅读 | 我想对大家说的话 | |----------------------------------------------------------------------------------------------------------------------------------------------------------------| | 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《[算法与数据汇总](https://blog.csdn.net/he_zhidan/article/details/137131684)》。 | | 学习算法:按章节学习《[喜缺全书算法册](https://download.csdn.net/download/he_zhidan/88348653)》,大量的题目和测试用例,[打包下载](https://blog.csdn.net/he_zhidan/article/details/140733677)。重视操作 | | 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 | | 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 | | 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 | | 如果程序是一条龙,那算法就是他的是睛 | | 失败+反思=成功 成功+反思=成功 | ### 视频课程 先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。 如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程 ### 测试环境 操作系统:win7 开发环境: VS2019 **C++17** 或者 操作系统:win10 开发环境: VS2022 **C++17** 如无特殊说明,本**算法** 用\*\*C++\*\*实现。 ![](https://i-blog.csdnimg.cn/blog_migrate/4b48f80cdf99b7ea9bda88ceb91d788f.gif)

相关推荐
三体世界27 分钟前
TCP传输控制层协议深入理解
linux·服务器·开发语言·网络·c++·网络协议·tcp/ip
你的冰西瓜1 小时前
C++ 中最短路算法的详细介绍
c++·算法·图论·最短路
<但凡.1 小时前
数据结构与算法之美:广义表
数据结构·c++·算法
偷偷的卷2 小时前
【算法笔记 day three】滑动窗口(其他类型)
数据结构·笔记·python·学习·算法·leetcode
大白的编程日记.3 小时前
【计算机基础理论知识】C++篇(二)
开发语言·c++·学习
C语言小火车3 小时前
野指针:C/C++内存管理的“幽灵陷阱”与系统化规避策略
c语言·c++·学习·指针
凤年徐3 小时前
【数据结构】时间复杂度和空间复杂度
c语言·数据结构·c++·笔记·算法
kualcal3 小时前
代码随想录17|二叉树的层序遍历|翻转二叉树|对称二叉树
数据结构·算法
踏莎行hyx3 小时前
使用langchain连接llama.cpp部署的本地deepseek大模型开发简单的LLM应用
c++·ai·langchain·大模型·llama.cpp·deepseek
山河木马3 小时前
前端学C++可太简单了:双冒号 :: 操作符
前端·javascript·c++