「网络流浅谈」最小割的模型

最大权闭合子图

引入 Introduction

闭合子图指对于子图 \(G=(V,E)\)\(\forall u \in V, (u,v)\in E\),都有 \(v\in V\)

最大权闭合子图无非就是对于所有的闭合子图 \(G\)\(\sum_{u\in V} w_u\) 最大的闭合子图。

对于这个图中,闭合子图有哪些呢?

红色框圈画出的即为 \(1\) 个闭合子图,因为对于任意一个点所连向的点都在该子图内。

主算法 Main Algorithm

不难发现任意一个割所划分成的 \(2\) 个集合均为闭合子图,而我们要求最大权闭合子图,故考虑如何求解。

建立一个流网络,将源点向所有权值为正的点连一条长度为该点权值的边,将所有权值为负数的点向汇点连一条长度为该点权值的绝对值的边。对于原图中的边,在流网络中均为 \(+\infty\)

对于割 \([S,T]\)\(c(S,T)=\sum_{(u,v)\in E,u\in S,v\in T}c(u,v)\),由于中间的边权均为 \(+\infty\),所以只会割两边的边,即 \(c(S,T)=\sum_{(s,v)\in E,v\in T}c(s,v)+\sum_{(u,t)\in E,u\in S}c(u,t)\)

又因为建图的时候 \(s\) 所连向的边,均为连向点的边权;而连向 \(t\) 的边的边权均为连向他点的权值。故,\(c(S,T)=\sum_{(s,v)\in E,v\in T}c(s,v)+\sum_{(u,t)\in E,u\in S}c(u,t)=\sum_{(s,v)\in E,v\in T}w_v-\sum_{(u,t)\in E,u\in S}w_u\)

考虑最大权闭合子图的权值为什么?\(val=\sum_{(s,v)\in E,v\in S}w_v+\sum_{(u,t)\in E,u\in S}w_u\)

不难发现第二项是完全一样的:所以将 \(c(S,T)\)\(val\) 相加得,\(c(S,T)+val=\sum_{(s,v)\in E,v\in T}w_v+\sum_{(s,v)\in E,v\in S}w_v\)

由于 \(S\)\(T\) 共同构成了点集 \(V\),故 \(\sum_{(s,v)\in E,v\in T}w_v+\sum_{(s,v)\in E,v\in S}w_v=\sum_{(s,v)\in E}w_v=\sum_{w_v>0}w_v\)

综上所述,\(val=\sum_{w_v>0}w_v-c(S,T)\),通过数学知识推理得 \(c(S,T)\) 应最小,即求最小割。

P4174 [NOI2006] 最大获利

模版题,按照上述做法建图即可。

#include <bits/stdc++.h>
#define fi first
#define se second
#define int long longusing namespace std;typedef pair<int, int> PII;
typedef long long LL;const int N = 6e4 + 10, M = 4e5 + 10, INF = 1e18;int n, m, s, t;
int a[N], b[N];
int h[N], e[M], ne[M], f[M], idx;
int dist[N], cur[N];void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx ++;e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx ++;
}
bool bfs() {memset(dist, -1, sizeof dist);queue<int> q;q.emplace(s), dist[s] = 0, cur[s] = h[s];while (q.size()) {auto u = q.front();q.pop();for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (dist[v] == -1 && f[i]) {dist[v] = dist[u] + 1, cur[v] = h[v];if (v == t) return 1;q.emplace(v);}}}return 0;
}
int find(int u, int lim) {if (u == t) return lim;int flow = 0;for (int i = cur[u]; ~i && flow < lim; i = ne[i]) {cur[u] = i;int v = e[i];if (dist[v] == dist[u] + 1 && f[i]) {int tmp = find(v, min(f[i], lim - flow));if (!tmp) dist[v] = -1;flow += tmp, f[i] -= tmp, f[i ^ 1] += tmp;}}return flow;
}
int dinic() {int res = 0, flow;while (bfs()) while (flow = find(s, INF)) res += flow;return res;
}signed main() {cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);cin >> n >> m;memset(h, -1, sizeof h);s = 0, t = n + m + 1;for (int i = 1; i <= n; i ++)cin >> a[i];for (int i = 1; i <= m; i ++) {int u, v;cin >> u >> v >> b[i];add(i + n, u, INF), add(i + n, v, INF);}int tot = 0;for (int i = 1; i <= m; i ++)add(s, i + n, b[i]), tot += b[i];for (int i = 1; i <= n; i ++)add(i, t, a[i]);cout << tot - dinic() << endl;return 0;
}

最大密度子图

引入 Introduction

定义无向图 \(G=(V,E)\) 的密度为 \(\frac{|E|}{|V|}\)。则,对于一个无向图 \(G=(V,E)\),令 \(G\) 的子图 \(G'=(V',E')\),满足 \(\forall (u,v)\in E', u\in V',v\in V'\)。对于所有满足条件的子图 \(G\) 中,密度最大的即为最大密度子图。

主算法 Main Algorithm

对于形式 \(\frac{|E|}{|V|}\),不难想到通过分数规划求解,即二分 \(g\),如果 \(\frac{|E|}{|V|}\ge g\),则说明 \(g\) 还可以更大,调整二分左端点;反之,调整二分右端点。

将式子继续化简可以得到:\(|E|-g|V|\ge 0\),也就是求 \(|E|-g|V|\) 的最大值,即 \(g|V|-|E|\) 的最小值。

最小割是可以解决点集的,但是难以算出边数的多少。所以,考虑如何将边数加入割。

考虑红色圈出的子图,如何计算边数呢?可以考虑使用度数,某个点度数减去该点连向集合外部的边的个数再除 \(2\),即可得到集合内边的个数,即 \(\frac{\sum_{u\in V}d_u-c(V,\bar V)}{2}\)。这样,就与割产生了关系。

继续推式子:\(g|V|-|E|=\sum_{u\in V}g-\frac{\sum_{u\in V}d_u-c(V,\bar V)}{2}=\sum_{u\in V}(g-\frac{d_u}{2})+\frac{c(V,\bar V)}{2}\)

为了使与最小割有单调关系,将割 \(c(V,\bar V)\) 的系数提出得 \(\frac{\sum_{u\in V}(2g-d_u)+c(V,\bar V)}{2}\)

那么,就可以建图了。对于任意一个点 \(u\) 均向汇点 \(t\) 连一条边权为 \(2g-d_u\) 的边,不过由于 \(2g-d_u\) 可能会小于 \(0\),所以边权应为 \(2g-d_u+U\),其中 \(U\) 为常数。对于点之间的边,即为原图的边,为了算边数所以边权均为 \(1\)。源点 \(s\) 向任意一个点连一条边权为 \(U\) 的边即可。\(U\)\(|E|\) 即可,因为 \(d_u\) 不可能超过 \(|E|\)。下图为一个例子。

建完图后,由于部分边权多加了 \(U\),所以考虑新图最小割 \(c'(S,T)\)\(|E|-g|V|\) 的关系。令 \(P=S-\{s\},P'=\bar P - \{t\}\),则最小割的边集分为 \(4\) 种情况:\(P\rightarrow \{t\},\{s\}\rightarrow P',P\rightarrow P',\{s\}\rightarrow \{t\}\)。不过,最后一种边不存在舍去。

\[\begin{aligned} c'(S,T)=&\sum_{u\in P} (U+2g-d_u)+\sum_{v\in P'}U+\sum_{u\in P}\sum_{v\in P'}c(u,v)\\ =&\sum_{u\in P}(U+2g-d_u+\sum_{v\in P'}c(u,v))+\sum_{v\in P'}U\\ =&\sum_{u\in P}(U+2g-(d_u-\sum_{v\in P'}c(u,v)))+\sum_{v\in P'}U\\ =&\sum_{u\in P}(U+2g-\sum_{v\in P}c(u,v))+\sum_{v\in P'}U\leftarrow u\ 所有出边-向集合外边=向集合内边\\ =&\sum_{u\in P}2g-\sum_{u\in P}\sum_{v\in P}c(u,v)+\sum_{v\in P'}U+\sum_{v\in P}U\\ =&|P|2g+ 2|E|+U\cdot n\\ \end{aligned} \]

故,\(|E|-g|V|=\frac{U\cdot n-c'(S,T)}{2}\),这里 \(V\)\(P\) 等价,都是我们选出的点。到此,该问题得以解决。

POJ3155 - Hard Life

模版题,使用上述做法建图计算即可。

对于输出方案,选择的集合其实就是最小割中 \(S\) 集合,那么怎么找出呢?只需要从 \(s\) 每次走 \(>0\) 的边所能到达的点的集合,便是答案(注意:不能包含 \(s\))。

#include <bits/stdc++.h>
#define fi first
#define se second
#define int long longusing namespace std;typedef pair<int, int> PII;
typedef long long LL;const int N = 1e2 + 10, M = 3e3 + 10;
const double eps = 1e-6;int n, m, s, t;
int h[N], e[M], ne[M], idx;
double f[M];
int d[N], cur[N], dg[N], st[N];
vector<int> res;
std::vector<PII> E;void add(int a, int b, double c1, double c2) {e[idx] = b, ne[idx] = h[a], f[idx] = c1, h[a] = idx ++;e[idx] = a, ne[idx] = h[b], f[idx] = c2, h[b] = idx ++;
}
bool bfs() {memset(d, -1, sizeof d);queue<int> q;q.emplace(s), d[s] = 0, cur[s] = h[s];while (q.size()) {int u = q.front();q.pop();for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (d[v] == -1 && f[i] > 0) {d[v] = d[u] + 1, cur[v] = h[v];if (v == t) return 1;q.emplace(v);}}}return 0;
}
double find(int u, double lim) {if (u == t) return lim;double flow = 0;for (int i = cur[u]; ~i && flow < lim; i = ne[i]) {cur[u] = i;int v = e[i];if (d[v] == d[u] + 1 && f[i] > 0) {double tmp = find(v, min(lim - flow, f[i]));if (tmp <= 0) d[v] = -1;f[i] -= tmp, f[i ^ 1] += tmp, flow += tmp;}}return flow;
}
void build(double g) {memset(h, -1, sizeof h);idx = 0;for (auto v : E) add(v.fi, v.se, 1, 1);for (int i = 1; i <= n; i ++) add(s, i, m, 0);for (int i = 1; i <= n; i ++) add(i, t, 2.0 * g - dg[i] + m, 0);
}
bool dinic(double g) {build(g);double res = 0, flow;while (bfs()) while (flow = find(s, 1e18)) res += flow;return res < m * n * 1.0;
}
void dfs(int u) {if (u != s) res.emplace_back(u);st[u] = 1;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (!st[v] && f[i] > 0) dfs(v);}
}signed main() {cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);cin >> n >> m;s = 0, t = n + 1;for (int i = 1; i <= m; i ++) {int u, v;cin >> u >> v;E.emplace_back(u, v), dg[u] ++, dg[v] ++;}double l = 0, r = m;while (r - l > eps) {double mid = (l + r) * 0.5;if (dinic(mid)) l = mid;else r = mid;}dinic(l), dfs(s);if (!res.size()) {cout << "1\n1";return 0;}cout << res.size() << endl;sort(res.begin(), res.end());for (auto v : res)cout << v << endl;cout << endl;return 0;
}

最小权点覆盖集

引入 Introduction

点覆盖集指选择点集 \(V\),使得对于边集 \(E\) 中的每一条边,至少有一个端点在点集 \(V\) 中。

最小权点覆盖集指在所有点覆盖集中,点的权值和最小的点集。

主算法 Main Algorithm

最小权点覆盖集只有在二分图的情况下才存在高效解,否则为 NPC 问题。

考虑如何将点覆盖集与割建立联系。对于一个点,如果割集中存在,那么说明点覆盖集中选择该点,同时在原图中与该点相连的点应不被割才符合题意,否则该边不存在任何点覆盖。

所以,网络流中的原图的边不能被割掉,故边权均为正无穷。不过,点是可以被割掉的,所以源点流向二分图一侧的每一个点,边权为该点的权值。从二分图的另一侧流向汇点,边权为该点的权值。(下图为示例)

不难发现,这样建立边权与原问题是等价的。考虑反证法,若存在一条边 \((u,v)\),点 \(u\) 和点 \(v\) 都没有被选择,那么说明源点连向 \(u\) 的边与 \(v\) 连向汇点的边均未被割,这说明残留网络中必然存在增广路(因为网络流中的 \((u,v)\) 边权为正无穷),与假设矛盾,证毕。

所以,在该网络流上跑最小割,即可求出原二分图的最小点全覆盖集。

代码 Code

#include <bits/stdc++.h>
#define fi first
#define se second
#define int long longusing namespace std;typedef pair<int, int> PII;
typedef long long LL;const int N = 点数, M = 边数;int n, m, s, t;
int h[N], e[M], ne[M], f[M], idx;
int d[N], cur[N], st[N];void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx ++;e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx ++;
}
bool bfs() {memset(d, -1, sizeof d);queue<int> q;q.emplace(s), d[s] = 0, cur[s] = h[s];while (q.size()) {int u = q.front();q.pop();for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (d[v] == -1 && f[i]) {d[v] = d[u] + 1, cur[v] = h[v];if (v == t) return 1;q.emplace(v);}}}return 0;
}
int find(int u, int lim) {if (u == t) return lim;int flow = 0;for (int i = cur[u]; ~i && flow < lim; i = ne[i]) {cur[u] = i;int v = e[i];if (d[v] == d[u] + 1 && f[i]) {int tmp = find(v, min(lim - flow, f[i]));if (!tmp) d[v] = -1;f[i] -= tmp, f[i ^ 1] += tmp, flow += tmp;}}return flow;
}
int dinic() {int res = 0, flow;while (bfs()) while (flow = find(s, 1e18)) res += flow;return res;
}
void dfs(int u) {st[u] = 1;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (!st[v] && f[i]) dfs(v);}
}signed main() {cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);memset(h, -1, sizeof h);cin >> n >> m;s = 0, t = 2 * n + 1;int w;for (int i = 1; i <= n; i ++)cin >> w, add(s, i, w);for (int i = n + 1; i <= n * 2; i ++)cin >> w, add(i, t, w);while (m -- ) {int a, b;cin >> a >> b;add(a, b + n, 1e18);}cout << dinic() << endl;return 0;
}

习题

POJ2125 - Destroying The Graph

最大权独立集

引入 Introduction

独立集指对于图 \(G(V,E)\),选出点集 \(V'\),使得对于 \(V'\) 中的任意 \(2\) 个点,\(2\) 点间都不存在一条边。

最大权独立集指对于所有独立集中点的权值和最大的独立集为最大权独立集。

主算法 Main Algorithm

最大权独立集 = 所有点权和 - 最小权点覆盖集

证明:

对于任意的点覆盖集 \(V_1\)\(V_1\)\(V\) 中的补集 \(V_2\) 恒为独立集。

证明:反证法。若不是独立集,说明存在边 \((u,v)\) 使得 \(u,v\in V_2\),那么由于 \(V_2\)\(V_1\) 的补集,所以 \(u,v\not\in V_1\),故 \(V_1\) 不是点覆盖集。与假设矛盾,证毕。

所以,\(\sum_{i\in V_1}w_i+\sum_{i\in V_2}w_i=\sum_{i=1}^n w_i\)

故,当前项(最小权点覆盖集)取最小时,后项(最大权独立集)取最大。

综上所述,只需要沿用最小权点覆盖集的求解方法,并用总和减去其权值即可。

代码 Code

#include <bits/stdc++.h>
#define fi first
#define se second
#define int long longusing namespace std;typedef pair<int, int> PII;
typedef long long LL;const int N = 点数, M = 边数;int n, m, s, t;
int h[N], e[M], ne[M], f[M], idx;
int d[N], cur[N], st[N];void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx ++;e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx ++;
}
bool bfs() {memset(d, -1, sizeof d);queue<int> q;q.emplace(s), d[s] = 0, cur[s] = h[s];while (q.size()) {int u = q.front();q.pop();for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (d[v] == -1 && f[i]) {d[v] = d[u] + 1, cur[v] = h[v];if (v == t) return 1;q.emplace(v);}}}return 0;
}
int find(int u, int lim) {if (u == t) return lim;int flow = 0;for (int i = cur[u]; ~i && flow < lim; i = ne[i]) {cur[u] = i;int v = e[i];if (d[v] == d[u] + 1 && f[i]) {int tmp = find(v, min(lim - flow, f[i]));if (!tmp) d[v] = -1;f[i] -= tmp, f[i ^ 1] += tmp, flow += tmp;}}return flow;
}
int dinic() {int res = 0, flow;while (bfs()) while (flow = find(s, 1e18)) res += flow;return res;
}
void dfs(int u) {st[u] = 1;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (!st[v] && f[i]) dfs(v);}
}signed main() {cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);memset(h, -1, sizeof h);cin >> n >> m;s = 0, t = 2 * n + 1;int w, tot = 0;for (int i = 1; i <= n; i ++)cin >> w, add(s, i, w), tot += w;for (int i = n + 1; i <= n * 2; i ++)cin >> w, add(i, t, w), tot += w;while (m -- ) {int a, b;cin >> a >> b;add(a, b + n, 1e18);}cout << tot - dinic() << endl;return 0;
}

习题

  1. P4474 王者之剑
  2. ABC354 G - Select Strings

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

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

相关文章

主流原型设计软件介绍

主流原型介绍 Axure Axure是一款强大的原型设计工具,它提供了丰富的功能,使设计师和产品经理能够快速创建互动原型和线框图,从而有效地传达设计思路和用户流程。 主要特点 1. 交互设计:Axure允许设计师创建高度互动的原型。通过拖放和设置各种交互动作(如点击、悬停、拖动等…

Strange Brigade(异域奇兵)全收集图文攻略

一、哈宾的挖掘场地 信件 1/7 起始位置往前走桥边帐篷桌子上猫猫 1/6 桥右转下坡转弯处猫猫 2/6 穿过洞穴第二个拱门上圣物 1/6 密室里信件 2/7 船边地上圣物 2/6 蝎子区进门靠右直走左转猫猫 3/6 蝎子区进门靠左上坡陶罐 1/4圣甲虫钥匙右边圣物 3/6 圣甲虫大门进去过场动画后一…

【DRF_02】基于Django CBV实现

路由系统from django.urls import path from app01.views import cbv_demourlpatterns = [# path(admin/, admin.site.urls),path(cbv/demo/, cbv_demo.StudentsView.as_view()), ]CBV视图from django.views import View from django.shortcuts import HttpResponse from djang…

考核复现-cnblog

考核复现 web 签到 源代码base64解密文件上传 先简单上传一个图片这里的类型被限制了,只允许上传哈哈类型的,修改一下吧上传成功 连接成功好玩的PHP 前一部分用数组绕过,后一部分将c赋值,用data伪协议绕过file_get_contents()函数 构造payload:?a[]=1&b[]=2&c=ab…

智能计算系统-Tensorflow框架的计算图机制

智能计算系统-Tensorflow框架的计算图机制 陈云霁老师的课,趁现在有时间,打算了解深度学习的底层原理。 从第五章编程框架机理开始,一到四章是深度学习基础,在此不再讨论 一. 深度学习框架的设计原则 1. 高性能 主要体现在神经网络的算子,针对底层硬件进行充分优化 在计算…

用户与组管理

一、服务器版本 windows服务器系统:win2003 、win2008、win2012、win2019 linux服务器系统:Redhat(开源收费(售后))、Centos(开源不收费) 二、用户概述1、每一个用户登录系统后,拥有不同的操作权限2、每个账户又自己唯一的SID(安全标识符) 为什么要分不同的用户 3、配…

ntfs

储备: 首先需要明白什么是文件系统。文件系统是系统对文件的存放排列方式,不同格式的文件系统关系到数据是如何在磁盘进行存储,文件名、文件权限和其他属性也存在不同。Windows操作系统支持 NTFS, FAT32, and exFAT三种不同文件系统。NTFS是目前Windows系统中一种现代文件系…

Chart.js (v2.9.4)概要介绍

chart.js是一个非常优秀的开源图表插件,扩展非常灵活,同时也提供了大量的钩子函数,给与用户添加自定义插件,实现个性化的需求。 具体的优势特点,这里不详述,网上大把资料,现开始正式深入了解这个插件.Chart布局大概分为如下六个区域,这些是主要的,也有些特殊,比如左右…

window版postgresql安装orafce插件

在Visual Studio中创建一个新的C工程:将解压目录下的除.sql和文件夹之外的文件都copy到新创建的工程中,另外要将sqlscan.c排除在项目之外 配置编译选项配置预处理器,预处理器定义中的内容为: WIN32 _WINDOWS _DEBUG _CRT_SECURE_NO_WARNINGS 注意要有_CRT_SECURE_NO_WARNI…

为什么 mov sp, 32,debug程序,执行sp=32的位置,后面的代码就全乱了(在小甲鱼零基础汇编第6章,包含多段程序,的视频代码)

assume cs:code, ds:data, ss:stackdata segmentdw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h; 用来作存放数据 data endsstack segmentdw 0, 0, 0, 0, 0, 0, 0, 0; 用来作栈的空间 stack ends code segmentstart:; 设置数据段mov ax, datamov ds, ax ; 设…

mysql报错:Lock wait timeout exceeded: try restadina transaction

这次是在Navicat上复制了一个表结构和数据准备备份一下,然后要用语句批量处理数据,结果导致项目上的更新操作报这个错误。原因是因为表中的数据量太大,复制表一时半会卡到那了。于是我在网上搜索了如下办法。尝试在数据库中杀死线程来终止复制表的操作。 SELECT * FROM info…

ASP.NET之JSONHelper操作

之前说到了Ext.Net中GridPanel行取值的问题(Ext.Net开发_GridPanel行选中取值),涉及到checkBox操作时,要留个心眼注意下取值的区别!返回值是Json格式。 现在用到了Json,就想自己也整一个Josn帮助类。在线帮助的资料很多,在巨人的身上东凑西凑也凑一个用用。   一、介绍…

Git:warning: CALF wilL be replaced by LF in xxxx 问题解决

warning: CALF wilL be replaced by LF in xxxx 问题解决办法 出现这个问题的原因是像缓存区中提交文件时出现的 原因: windows中的换行符为 CRLF,而在Linux下的换行符为LF,所以在执行add . 时出现提示 也就是, 工作区的文件都应该用 CRLF 来换行。如果 改动文件时引入了 …

密码爆破ssh与ftp服务(finish)

密码爆破ssh与ftp服务 使用工具九头蛇(hydra) ssh 环境配置 win10 安装sshd服务端在cmd命令行使用 net start sshd 命令启动服务kali 打开终端查看是否开启ssh服务 nmap -sV -T4 -p- [kali的ip] 先创建一个用户名字典username.txt,把经常用的用户名写入到字典中 touch usern…

Obsidian第三方插件下载

一、从Obsidian插件市场下载 使用第三方插件的第一步是关闭安全模式。点击“浏览”,搜索需要的插件名称。在这里输入名称搜索。点击安装。点击启用。必要时要使用魔法。 替代方法proxy-github二、从别的地方下载的插件 首先了解一下obsidian插件的基本构成,主要包括下面的文件…

鸿蒙4.2小版本大亮点,鸿蒙5.0也不远了

混合应用开发技术,特别是结合小程序和原生技术,为鸿蒙应用开发带来了显著的优势。首先,它简化了开发流程,使开发者能够迅速创建出高质量的应用程序。这不仅缩短了开发周期,还提升了应用的整体性能和用户体验。上个月,市场上迎来了华为鸿蒙系统4字开头的小升级,版本来到了…

JPA和Hibernate的乐观锁与悲观锁

哈喽,大家好,我是木头左!JPA和Hibernate的乐观锁和悲观锁 乐观锁 乐观锁是一种假设资源不会被冲突影响的并发控制策略。它假设多个事务在同一时间内不会发生冲突,因此不需要加锁。当事务提交时,如果检测到数据发生了改变,就会抛出异常,让开发者决定如何处理这个冲突。 在…

树形DP

树形 DP 即在树上进行的 DP。 常见的两种转移方向:父节点 \(\rightarrow\) 子节点:如求节点深度,\(dp_u = dp_{fa} + 1\) 子节点 \(\rightarrow\) 父节点:如求子树大小,\(dp_u = 1 + \sum dp_v\)习题:P5658 [CSP-S2019] 括号树暴力 本题 \(n\) 小的数据点保证为链,直接枚…