虚树

news/2025/1/13 17:49:08/文章来源:https://www.cnblogs.com/Baiyj/p/18242912

虚树

什么是虚树

虚树常常被用在树形\(dp\)中。当一次询问仅仅涉及到整棵树中少量节点时为每次询问都对整棵树进行\(dp\)在时间上是不可接受的。此时,我们建立一棵仅仅包含部分关键节点的虚树,将非关键节点构成的链简化成边或是剪去,在虚树上进行\(dp\)
虚树包含所有的询问点及它们之间的\(lca\)。显然虚树的叶子节点必然是询问点,因此对于某次含有\(k\)个点的询问,虚树最多有\(k\)个叶子节点,从而整棵虚树最多只有\(2^{k-1}\)个节点(这会在虚树变为二叉树形态时达到)。

建立虚树之前

我们需要:

  • 预处理出原树的\(dfs\)序以及\(dp\)可能用到的一些其他东西。
  • 高效的在线\(LCA\)算法,单词询问\(O(log n)\)的倍增和树剖
  • \(O(1)\)\(RMQ/ST\)
  • 将询问点按\(dfs\)序排序

如何建立虚树

最右链是虚树构建的一条分界线,表明其左侧部分的虚树已经完成构建。我们使用栈来维护最右链,\(top\)为栈顶位置。值得注意的是,最右链上的边并没有被加入虚树,这是因为在接下来的过程中随时会有某个\(lca\)插入到最右链中。
初始无条件将第一个询问点加入栈中。
将接下来所有的询问点顺次加入。假设该询问点为\(now\)\(lc\)为该点和栈顶点的\(LCA\)(即\(lc=LCA(stak[top],now)\))。由于\(lc\)\(stak[top]\)的祖先,\(lc\)必然在我们维护的最右链上。
然后考虑\(lc\)\(stak[top]\)及栈中第二个元素\(stak[top-1]\)的关系。

情况一:

\(lc=stak[top]\),也就是说,\(now\)\(stak[top]\)的子树中。

这时候,我们只需要把\(now\)入栈,即把它加到最右链的末端即可。

情况二:

\(lc\)\(stak[top]\)\(stak[top-1]\)之间。

显然,此时最右链的末端从\(stak[top-1]\to stak[top]\)变成了\(stak[top-1]\to lc\to stak[top]\),我们需要做的,首先是把边\(lc-stak[top]\)加入虚树,然后把\(stak[top]\)出栈,把\(lc\)\(now\)入栈。

情况三:

\(lc=stak[top-1]\)

这种情况和第二种差不多,只是lc不用入栈了。

情况四:

此时有\(dep[lc]<dep[stak[top-1]]\)\(lc\)已经不在\(stak[top-1]\)的子树中了,甚至也未必在\(stak[top-2],stak[top-3]\)……的子树中了。

以图中为例,最右链从\(stak[top-3]\to stak[top-2]\to stak[top-1]\to stak[top]\)变成了\(stak[top-3]\to lc\to now\)。我们需要依次将最右链的末端剪下,将被剪下的边加入虚树,直到不再是情况四。
就上图而言,循环会持续两轮,将\(stak[top],stak[top-1]\)依次出栈,并且把边\(stak[top-1]-stak[top],stak[top-2]-stak[top-1]\)加入虚树中,随后通过情况二完成构建。
当最后一个询问点加入之后,再将最右链加入虚树,即可完成构建。

一些问题

1.如果栈中仅仅有一个元素,此时stak[top-1]是否会出问题?
对于栈,我们从1开始储存。那么在这种情况下,\(stak[top-1]=0\),并且\(dep[0]=0\)。此时\(dep[lc]>dep[stak[top-1]]\)恒成立。也就是说,\(stak[0]\)扮演了深度最小的哨兵,确保了程序只会进入情况一和二。

2.如何在一次询问结束后清空虚树?
不能直接对图进行清空,否则复杂度会退化到\(O(n)\),这是无法承受的。在\(dfs\)的过程中每当访问完一个节点就进行清空即可。

例题1:[SDOI2011] 消耗战

首先来看朴素的树形dp。
\(dp_(i)\)表示使i不与其子树中任意一个关键点连通的最小代价。设\(w(a,b)\)表示a与b之间的边的权值。
则枚举i的儿子v:

  • 若v不是关键点:\(dp(i)=dp(i)+min\{dp(v),w(i,v)\}\)
  • 若v是关键点:\(dp(i)=dp(i)+w(i,v)\)

但我们可以发现树中的很多节点都是没有用的,所以我们需要浓缩信息,把一棵大树浓缩成一棵小树,即建立虚树。
具体地,我们先在栈中添加节点1,然后按照dfs序从小到大添加关键节点。添加的过程按照上述建立即可。
最后在虚树上跑dp即可。
值得注意的是,即使当前节点是关键节点,用不到\(dp(u)\)的值,但仍要对其儿子进行dfs,因为清空虚树需要对整个虚树进行遍历。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define ll long long
using namespace std;
const int N = 5e5 + 3;
const ll inf = LLONG_MAX;
int dfn[N], dep[N], fa[N][21];
ll minv[N];  //维护从根节点到该点的最小边权,因为要拆代价最小的边
int h[N];
bool vis[N];
int n, m, k, tot;
int stak[N], top;
struct Tree {struct edge {int to, nxt;ll val;} e[N << 1];int head[N], cnt;  //存原图 inline void add(int x, int y, ll v) {e[++cnt].nxt = head[x];head[x] = cnt;e[cnt].to = y;e[cnt].val = v;}inline void dfs(int u, int fath) {dfn[u] = ++tot;fa[u][0] = fath;for (rg int i = 1; i <= 19; i++) {fa[u][i] = fa[fa[u][i - 1]][i - 1];}for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to;if (v == fath) continue ;dep[v] = dep[u] + 1;minv[v] = min(minv[u], e[i].val);dfs(v, u);}}
} T;
struct VirtualTree {struct edge {int to, nxt;} e[N << 1];int head[N], cnt;  //存虚树,建单向边 inline void add(int x, int y) {e[++cnt].nxt = head[x];e[cnt].to = y;head[x] = cnt;}inline ll dfs(int u) {rg ll sum = 0, res;for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to;sum += dfs(v);}if (vis[u]) res = minv[u];  //是关键节点,必须拆 else res = min(minv[u], sum);  //不是关键节点 vis[u] = false;  //清空虚树head[u] = 0;return res;}inline int LCA(int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (rg int i = 19; i >= 0; i--) {if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];}if (u == v) return u;for (rg int i = 19; i >= 0; i--) {if (fa[u][i] != fa[v][i]) {u = fa[u][i];v = fa[v][i];}}return fa[u][0];}inline void build() {cnt = 0;top = 1;stak[top] = h[1];  //先将1入栈for (rg int i = 2; i <= k; i++) {rg int now = h[i];rg int lc = LCA(now, stak[top]);while (true) {if (dep[lc] >= dep[stak[top - 1]]) {if (lc != stak[top]) {  //不满足为情况一add(lc, stak[top]);if (lc != stak[top - 1]) {  //情况二 stak[top] = lc;} else {  //情况三 top--;}}break;} else {  //情况四add(stak[top - 1], stak[top]);top--;}}stak[++top] = now;  //将now入栈 }while (--top) {add(stak[top], stak[top + 1]);  //将最右链加入虚树 }}
} VT;
inline bool cmp(int a, int b) {return dfn[a] < dfn[b];
}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);minv[1] = inf;cin >> n;for (rg int i = 1; i < n; i++) {rg int x, y;rg ll v;cin >> x >> y >> v;T.add(x, y, v);T.add(y, x, v);}T.dfs(1, 0);cin >> m;while (m--) {cin >> k;for (rg int i = 1; i <= k; i++) {cin >> h[i];vis[h[i]] = true;  //给关键点打上标记}sort(h + 1, h + k + 1, cmp);  //按dfs序对关键节点进行排序VT.build();cout << VT.dfs(stak[1]) << "\n";}return qwq;
}

例题2:[SDOI2015] 寻宝游戏

可以发现,对于按照dfs序排序后的关键节点\(\{a_1,a_2,\cdots,a_k\}\)\(dis(a_1,a_2)+dis(a_2,a_3)+\cdots+dis(a_{k-1},a_k)+dis(a_k,1)\)即为最短路程。
于是预处理出每个节点到根节点的距离来计算任意两节点的距离。对于关键节点,使用set存储即可。
假设插入x,它的dfs序的左右两边分别是y和z。那么答案加上\(dis(x,y)+dis(x,z)-dis(y,z)\)。删除时减去即可。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define int long long
using namespace std;
const int N = 1e5 + 3;
int dis[N], dep[N], fa[N][21];
int dfn[N], tot;
int head[N], cnt;
struct edge {int to, nxt;int val;
} e[N << 1];
inline void add(int x, int y, int z) {e[++cnt].to = y;e[cnt].val = z;e[cnt].nxt = head[x];head[x] = cnt;
}
inline void dfs(int u, int fath) {dfn[u] = ++tot;fa[u][0] = fath;for (rg int i = 1; i <= 19; i++) {fa[u][i] = fa[fa[u][i - 1]][i - 1];}for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to, w = e[i].val;if (v == fath) continue ;dep[v] = dep[u] + 1;dis[v] = dis[u] + w;dfs(v, u);}
}
inline int LCA(int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (rg int i = 19; i >= 0; i--) {if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];}if (u == v) return u;for (rg int i = 19; i >= 0; i--) {if (fa[u][i] != fa[v][i]) {u = fa[u][i];v = fa[v][i];}}return fa[u][0];
}
inline int get_dis(int x, int y) {return dis[x] + dis[y] - 2 * dis[LCA(x, y)];
}
int n, m;
bool vis[N];
struct node {int val;int dfn;bool operator < (const node& tmp) const { return dfn < tmp.dfn; }
};
set<node> s;
set<node>::iterator it;
int ans;
signed main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin >> n >> m;for (rg int i = 1; i < n; i++) {rg int x, y, z;cin >> x >> y >> z;add(x, y, z);add(y, x, z);}dfs(1, 0);while (m--) {rg int x, y, z;rg node nod;cin >> x;if (!vis[x]) s.insert({x, dfn[x]});  //先插入,保证任何时候s中元素个数不为0 it = s.lower_bound({x, dfn[x]});nod = (it == s.begin() ? *--s.end() : *--it);y = nod.val;it = s.upper_bound({x, dfn[x]});nod = (it == s.end() ? *s.begin() : *it);z = nod.val;rg int d = get_dis(x, y) + get_dis(x, z) - get_dis(y, z);if (!vis[x]) {vis[x] = true;ans += d;} else {s.erase({x, dfn[x]});vis[x] = false;ans -= d;}cout << ans << "\n";}return qwq;
}

例题3:[HEOI2014] 大工程

先建虚树。
首先来看代价和:
可以发现,一条与询问节点相连的边对代价和的贡献为该询问节点的子树中询问节点的个数(包括该节点)乘不在该子树中的询问节点个数再乘边权,即\(siz[u]* (k-siz[u]) * w\),其中\(siz[u]\)表示u节点的子树内询问节点的个数。
再来看最大代价:
\(maxx[u]\)表示u的子树中最长链的长度,每次用已经遍历过的子树中的最长链与当前子树中的最长链相拼来更新答案。
对于最小代价:
\(minn[u]\)表示u的子树中最短链的长度,初始时将询问节点设为0,非询问节点设为极大值,这样,若u为询问节点,则v子树中的最短链只需加上\(dis(u,v)\)就是答案;若u为非询问节点,则v子树中的最短链拼上已经遍历过的子树中的最短链来更新答案。

#include<bits/stdc++.h>
#define rg register
#define qwq 0
#define ll long long
using namespace std;
const int N = 1e6 + 3, inf = 2e9;
int n, q, k;
int d[N];
bool vis[N];
int dep[N], fa[N][21];
int dfn[N], tot;
inline bool cmp(int a, int b) {return dfn[a] < dfn[b];
}
inline int LCA(int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (rg int i = 20; i >= 0; i--) {if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];}if (u == v) return u;for (rg int i = 20; i >= 0; i--) {if (fa[u][i] != fa[v][i]) {u = fa[u][i];v = fa[v][i];}}return fa[u][0];
}
struct Tree {int head[N], cnt;struct edge {int to, nxt;} e[N << 1];inline void add(int x, int y) {e[++cnt].to = y;e[cnt].nxt = head[x];head[x] = cnt;}inline void dfs(int u, int fath) {dep[u] = dep[fath] + 1;dfn[u] = ++tot;fa[u][0] = fath;for (rg int i = 1; i <= 20; i++) {fa[u][i] = fa[fa[u][i - 1]][i - 1];}for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to;if (v == fath) continue ;dfs(v, u);}}
} T;
ll ans1;
int ans2, ans3;
int stak[N], top;
struct VirtualTree {int head[N], cnt;struct edge {int to, nxt;} e[N << 1];inline void add(int x, int y) {e[++cnt].to = y;e[cnt].nxt = head[x];head[x] = cnt;}int sum[N], minn[N], maxx[N], siz[N];inline void dfs(int u, int fath) {siz[u] = vis[u];maxx[u] = 0;minn[u] = (vis[u] ? 0 : inf);for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to;if (v == fath) continue ;dfs(v, u);}for (rg int i = head[u]; i; i = e[i].nxt) {rg int v = e[i].to, w = dep[v] - dep[u];if (v == fath) continue ;ans1 += 1ll * (k - siz[v]) * siz[v] * w;if (siz[v]) {//因为若u为询问节点则minn[u]为0,此时用minn[v]+w(即当前子树的最短链加dis(u,v))来更新答案//若u为非询问节点则minn[u]为已经遍历过的子树中的最短链,此时将两链相拼更新答案 ans2 = min(ans2, minn[u] + minn[v] + w); ans3 = max(ans3, maxx[u] + maxx[v] + w);}minn[u] = min(minn[u], minn[v] + w);maxx[u] = max(maxx[u], maxx[v] + w);siz[u] += siz[v];}vis[u] = 0;head[u] = 0;}inline void build() {cnt = 0;top = 1;stak[top] = d[1];for (rg int i = 2; i <= k; i++) {rg int now = d[i];rg int lca = LCA(now, stak[top]);while (true) {if (dep[lca] >= dep[stak[top - 1]]) {if (lca != stak[top]) {add(lca, stak[top]);add(stak[top], lca);if (lca != stak[top - 1]) {stak[top] = lca;} else {top--;}}break;} else {add(stak[top - 1], stak[top]);add(stak[top], stak[top - 1]);top--;}}stak[++top] = now;}while (--top) {add(stak[top], stak[top + 1]);add(stak[top + 1], stak[top]);}}
} VT;
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin >> n;for (rg int i = 1; i < n; i++) {rg int u, v;cin >> u >> v;T.add(u, v);T.add(v, u);}T.dfs(1, 0);cin >> q;while (q--) {cin >> k;for (rg int i = 1; i <= k; i++) {cin >> d[i];vis[d[i]] = true;}sort(d + 1, d + k + 1, cmp);VT.build();ans1 = 0;ans2 = inf;ans3 = 0;VT.dfs(stak[1], 0);cout << ans1 << " " << ans2 << " " << ans3 << "\n";}return qwq;
}

例题4:[HNOI2014] 世界树

首先,要计算每一个点离它最近的议事点,可以通过两遍dfs完成,第一遍用儿子更新父亲,第二遍用父亲更新儿子。
建完虚树后,分成两种情况维护虚树上的点:

  • 1.一个虚树点u,如果它在原树上的某个儿子v的整棵子树都没有议事点,那么v子树的大小就能贡献到p。
  • 2.对于虚树上的一条边,它的两端u和v(即使是lca也可以),对应到原树上的一条链,要找到这条链上的分界点,一半各属于一个议事点。注意链上的子树的贡献也要算上。
#include<bits/stdc++.h>
#define rg register
#define qwq 0
using namespace std;
const int N = 3e5 + 3, inf = 2e9;
int n, q, m;
int dfn[N], tot, siz[N], dep[N], fa[N][20];
int h[N], oh[N];
bool vis[N];
int ans[N];
int dp[N], g[N];  //距议事点的最短距离,最近的议事点 
int head1[N], cnt1, head2[N], cnt2;
struct edge {int to, nxt;
} e1[N << 1], e2[N << 1];
inline void add1(int x, int y) {e1[++cnt1].to = y;e1[cnt1].nxt = head1[x];head1[x] = cnt1;e1[++cnt1].to = x;e1[cnt1].nxt = head1[y];head1[y] = cnt1;
}
inline void add2(int x, int y) {e2[++cnt2].to = y;e2[cnt2].nxt = head2[x];head2[x] = cnt2;e2[++cnt2].to = x;e2[cnt2].nxt = head2[y];head2[y] = cnt2;
}
inline bool cmp(int a, int b) {return dfn[a] < dfn[b];
}
inline void dfs(int u, int fath) {dfn[u] = ++tot;dep[u] = dep[fath] + 1;siz[u] = 1;fa[u][0] = fath;for (rg int i = 1; i <= 19; i++) {fa[u][i] = fa[fa[u][i - 1]][i - 1];}for (rg int i = head1[u]; i; i = e1[i].nxt) {rg int v = e1[i].to;if (v == fath) continue ;dfs(v, u);siz[u] += siz[v];}
}
inline int LCA(int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (rg int i = 19; i >= 0; i--) {if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];}if (u == v) return u;for (rg int i = 19; i >= 0; i--) {if (fa[u][i] != fa[v][i]) {u = fa[u][i];v = fa[v][i];}}return fa[u][0];
}
int stak[N], top;
inline void build() {cnt2 = 0;top = 1;stak[top] = 1;for (rg int i = 1; i <= m; i++) {if (h[i] == 1) continue ;rg int now = h[i];rg int lca = LCA(now, stak[top]);while (true) {if (dep[lca] >= dep[stak[top - 1]]) {if (lca != stak[top]) {add2(lca, stak[top]);if (lca != stak[top - 1]) {stak[top] = lca;} else {top--;}}break;} else {add2(stak[top - 1], stak[top]);top--;}}stak[++top] = now;}while (--top) {add2(stak[top], stak[top + 1]);}
}
inline void cal(int x, int y) {  //寻找分割点 rg int u = y, v = y;for (rg int i = 19; i >= 0; i--) {if (dep[fa[u][i]] > dep[x]) u = fa[u][i];  //跳到在原树上对应的x的儿子}ans[g[x]] -= siz[u];for (rg int i = 19; i >= 0; i--) {rg int llen = dep[y] - dep[fa[v][i]] + dp[y];rg int rlen = dep[fa[v][i]] - dep[x] + dp[x];if (dep[fa[v][i]] > dep[x] && (llen < rlen || (llen == rlen && g[y] < g[x]))) v = fa[v][i];}ans[g[y]] += siz[v] - siz[y];ans[g[x]] += siz[u] - siz[v];
}
inline void dfs1(int u, int fath) {  //用儿子更新父亲 dp[u] = inf;for (rg int i = head2[u]; i; i = e2[i].nxt) {rg int v = e2[i].to, w = dep[v] - dep[u];if (v == fath) continue ;dfs1(v, u);if (dp[v] + w < dp[u]) {dp[u] = dp[v] + w;g[u] = g[v];} else if (dp[v] + w == dp[u]) {g[u] = min(g[u], g[v]);}}if (vis[u]) {dp[u] = 0;g[u] = u;}
}
inline void dfs2(int u, int fath) {  //用父亲更新儿子 for (rg int i = head2[u]; i; i = e2[i].nxt) {rg int v = e2[i].to, w = dep[v] - dep[u];if (v == fath) continue ;if (dp[u] + w < dp[v]) {dp[v] = dp[u] + w;g[v] = g[u];} else if (dp[u] + w == dp[v]) {g[v] = min(g[v], g[u]);}cal(u, v);dfs2(v, u);}ans[g[u]] += siz[u];  //需要加上自己 vis[u] = 0;head2[u] = 0;
}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin >> n;for (rg int i = 1; i < n; i++) {rg int u, v;cin >> u >> v;add1(u, v);}dfs(1, 0);cin >> q;while (q--) {cin >> m;for (rg int i = 1; i <= m; i++) {cin >> h[i];oh[i] = h[i];vis[h[i]] = true;}sort(h + 1, h + m + 1, cmp);build();dfs1(stak[1], 0);dfs2(stak[1], 0);for (rg int i = 1; i <= m; i++) {cout << ans[oh[i]] << " ";vis[oh[i]] = false;ans[oh[i]] = 0;}cout << "\n"; }return qwq;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/723300.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

矩阵乘法与矩阵快速幂

1 矩阵乘法 1.定义 若矩阵A的大小为\(n \times m\),矩阵B的大小为\(m \times p\),则两个矩阵可以做乘法,得到的矩阵C的大小为\(n \times p\)。 \[A = \begin{bmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \end{bmatrix} \]\[B = \begin{…

状压dp

状压dp 1.状态压缩 状态压缩就是使用某种方法,以最小的代价来表示某种状态,通常是用一串01数字(二进制数)来表示各个点的状态。这就要使用状态压缩的对象的点的状态只有两种:0和1。 2.使用条件 1.解法需要保存一定的状态数据(表示一种状态的一个数据值),每个状态通常情…

Redis之哨兵模式

概述无哨兵模式的主从切换的方法是当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。 Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题…

ibus-libpinyin无法使用中括号下一页(未解决)

问题 中括号切换上下页用惯了,使用这个不能【】真不习惯。 SunPinyin sunpinyin可以[]切换,但是【】本身变成其他字符了。 不能使用shift将半路的中文换为英文。比如我打拼音,输入完后按shift,我希望他变成英文,并且切换到英文。 搜狗输入法 系统是Ubuntu 22.04 搞了半天用…

m基于FPGA的FIR低通滤波器实现和FPGA频谱分析,包含testbench和滤波器系数MATLAB计算程序

1.算法仿真效果 本系统进行了Vivado2019.2平台的开发,Vivado2019.2仿真结果如下:整体仿真结果如下:放大看,滤波效果如下:对应的频谱如下:FPGA的RTL结构如下:最后用matlab对比仿真,结果如下:可以看到,FPGA的滤波效果和频谱分析与matlab的结果一致。2.算法涉及理论知识…

ch6 信息商品

ch6 信息商品课程目标熟悉信息商品的形成和信息劳动价值理论 掌握信息商品的特征分析及信息商品的价格理论 运用信息商品价格理论分析和理解相关案例多看两眼 ppt 吧,这一部分考的很多 知识回顾 信息商品的概念信息成为商品的基本条件 不宜成为信息商品的信息产品类型 信息商品…

原始套接字

解析MAC数据包原始套接字-解析MAC数据包 原始套接字.c 套接字类型 原始套接字 1、一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心 2、可以接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用 3、开发人员可发送自己组装的数据包到网…

http与cJSON练习

http与cJSON练习 /**************************************************************************************************** * weather_test.c * 练习tcp连接,与http协议,调用服务器接口,实现获取特定城市的天气信息…

多线程实现并发

多线程实现并发案例多线程并发服务器 架构 void* thread_fun(void* arg) {while(1){recv()/send()} }scokfd = socket() bind() listen() while(1){accept()//连上就创建线程pthread_create(, ,thread_fun, )pthread_detach() }案例 /* # Multi-process concurrent server…

利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJSON库对服务器的响应数据进行解析,并输出到终端

目录题目分析代码结果 题目利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJSON库对服务器的响应数据进行解析,并输出到终端 分析1.需从源代码网站GitHub或SourceForge代码网站下载cJSON库及阅读下载的README相关手册如何使用cJSON库…

[DP] [倍增优化] Luogu P1081 [NOIP2012 提高组] 开车旅行

[NOIP2012 提高组] 开车旅行 题目描述 小 \(\text{A}\) 和小 \(\text{B}\) 决定利用假期外出旅行,他们将想去的城市从 $1 $ 到 \(n\) 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 \(i\) 的海拔高度为\(h_i\),城市 \(i\) 和城市 \…

【esp32 学习笔记】让SD卡发光发热~

原理图:图 SD卡部分图 MCU中与SD卡相关的接口 连接关系如下:[ESP32 IO26 – CS MICROSD] [ESP32 IO23 – MOSI(DI) MICROSD] [ESP32 IO19 – MISO(DO) MICROSD] [ESP32 IO18 – SCK MICROSD] [ESP32 GND – GND MICROSD] [3.3V – VCC MICROSD] 软件: 我们将使用SD卡库…