动态规划求解最小斯坦纳树(证了一天两夜)

news2024/9/23 1:38:14

最小斯坦纳树

给定点的“最小生成树”问题。

背景

给定无向连通图 G = ( V , E ) G=(V,E) G=(V,E),给出包含 k k k 个结点的点集 S S S,包含点集 S S S 的连通图被称作 斯坦纳树。但我们关注的是如何求出包含点集 S S S最小连通图 G ′ = ( V ′ , E ′ ) G'=(V',E') G=(V,E)最小斯坦纳树。其中 S ⊆ V ′ , E ′ ⊆ E S\subseteq V',E'\subseteq E SV,EE

此处的最小可以指最小的点权和边权和等。

求解最小斯坦纳树是一个 N P − H a r d \color{red}NP-Hard NPHard 问题,所以只会有近似解,并且数据范围不会太大即 k ≤ 10 k\leq10 k10

目前的最好算法的算法时间复杂度为: O ( 3 K N + 2 K M l o g M ) O(3^KN+2^KMlogM) O(3KN+2KMlogM)。其中 N = ∣ V ∣ , M = ∣ E ∣ N=|V|,M=|E| N=V,M=E

动态规划+状态压缩

笔者对着题解思考了这个方法近 1 1 1 天半,并网上查阅了很多资料,再加上自己的一些证明,终于能够彻底搞懂这个算法的精髓。下文将着重论述笔者当时卡住的地方。

我们先假设求的是包含点集 S S S边权和最小的连通块。设权重函数 W : E → N W:E\rarr N W:EN

【起源】

【性质 1 1 1包含点集 S S S边权和最小的子图 G ′ = ( V ′ , E ′ ) G'=(V',E') G=(V,E) 一定是一棵

证明】若 G ′ G' G 中包含环,那么将环中边权最大的边删去后, G ′ G' G 仍连通且边权和变得更小,与前面假设的边权和最小矛盾,故 G ′ G' G 不含环,证毕。

根据性质 1 1 1,我们不难得到一个暴力算法:枚举 G G G 的所有子图 ,对子图进行最小生成树算法。虽然这样一定能够求出包含 S S S 的斯坦纳树,但时间复杂度是 O ( 2 V E l o g V ) O(2^{V}ElogV) O(2VElogV),这是因为有许多子图是没必要枚举的,且每次枚举后还要进行生成树算法,不如我们干脆就从包含 S S S 的树开始转移

d p [ i ] [ S ] dp[i][S] dp[i][S] 表示以 i i i 为根结点且包含 S S S 的树的最小边权和,因为 S S S 是一个集合,且集合内的元素不超过 10 10 10,所以我们考虑用二进制来表示这个集合。

到这里,几乎所有的题解/博客都是直接给出转移方程,然后再贴个代码。但里面的内容却不细讲,可能是觉得大家都学到斯坦纳树了,没必要再写太过详尽。但是却苦了我这个菜鸟。

为了理解透彻我们先从初始化开始讲。

【初始化】

S = { t e r m i n a l [ 1 ] , t e r m i n a l [ 2 ] , ⋯   , t e r m i n a l [ k ] } S=\{terminal[1],terminal[2],\cdots,terminal[k]\} S={terminal[1],terminal[2],,terminal[k]} 。(最初定义斯坦纳树的论文中,管 S S S 中的点叫做终端( t e r m i n a l terminal terminal)点,斯坦纳树中的不属于终端点的其他点叫做斯坦纳点。)那么则有初始化:

for i in (1,2,...,k) :
     dp[terminal[i]][1<<(i-1)] = 0;

d p [ t e r m i n a l [ i ] ] [ 1 < < ( i − 1 ) ] dp[terminal[i]][1<<(i-1)] dp[terminal[i]][1<<(i1)] 中,根是终端点本身,包含的集合也是终端点本身。因为算的是子树的最小边权和,而只有一个点的树显然没有边,所以赋值为 0 0 0

数组第二维的 ( i − 1 ) (i-1) (i1) 是为了和集合 S S S 的二进制表示所对齐,因为 S S S 的二进制表示是 1 ⋯ 1 ⏟ k 个 1 \underbrace{1\cdots 1}_{k个1} k1 11,即 ( 1 < < k ) − 1 (1<<k)-1 (1<<k)1,而 1 < < ( i − 1 ) 1<<(i-1) 1<<(i1) 正好是 S S S 的二进制中第 i i i 1 1 1

初始化了解完了之后,我们需要了解答案是怎么产生的。

【答案产生】

{ d p [ i ] [ S ] ; ∀ i ∈ S } \{dp[i][S];\forall i\in S\} {dp[i][S];iS} 显然都是正确答案,因为 d p [ i ] [ S ] dp[i][S] dp[i][S] 表示以 i i i 为根结点且包含 S S S 的树的最小边权和,而最小斯坦纳树显然包括 S S S,所以 S S S 中的任意一点为根都可以作为答案。

因为最终答案的 S S S 的二进制表示中有 k k k 1 1 1,但初始化时只有一个 1 1 1,所以 d p dp dp 的转移过程中必然需要子集合的合并。

比如说 d p [ i ] [ 00 ⋯ 10 ] dp[i][00\cdots10] dp[i][0010] d p [ i ] [ 00 ⋯ 01 ] dp[i][00\cdots01] dp[i][0001] 可以合并为 d p [ i ] [ 00 ⋯ 11 ] dp[i][00\cdots11] dp[i][0011] d p dp dp 的第二维是集合的二进制表示)。

看到这里可能会感到好奇,因为初始化时 d p dp dp 数组中根和集合都是终端点本身,即一个点对应一个集合,但上文的 d p [ i ] [ 00 ⋯ 10 ] dp[i][00\cdots10] dp[i][0010] d p [ i ] [ 00 ⋯ 01 ] dp[i][00\cdots01] dp[i][0001] 却是一个点对应两个集合。这是怎么从初始态转移而来的?

我们定义 d ( i , j ) d(i,j) d(i,j) 为原图中 i ⇝ j i\leadsto j ij最短路径长度。我们强行让 j j j 结点和 一棵以 t e r m i n a l [ i ] terminal[i] terminal[i] 为根,包含集合 1 < < ( i − 1 ) 1<<(i-1) 1<<(i1) 的子树连通,那么连通后就有了一棵新树。以 j j j 为根,包含集合 1 < < ( i − 1 ) 1<<(i-1) 1<<(i1) 的子树的最小边权和是:
d p [ j ] [ 1 < < ( i − 1 ) ] = d p [ t e r m i n a l [ i ] ] [ 1 < < ( i − 1 ) ] + d ( t e r m i n a l [ i ] , j ) (1) dp[j][1<<(i-1)] = dp[terminal[i]][1<<(i-1)] + d(terminal[i],j)\tag{1} dp[j][1<<(i1)]=dp[terminal[i]][1<<(i1)]+d(terminal[i],j)(1)
也就是在原来的基础上加上了 t e r m i n a l [ i ] terminal[i] terminal[i] j j j 的最短路。这个式子的正确性是显然的。

接下来我们将演示在一个简单图上的 d p dp dp 过程。

请看下图演示

S = { 1 , 3 } S=\{1,3\} S={1,3}

在这里插入图片描述

初始化

d p [ 1 ] [ 01 ] = 0 , d p [ 3 ] [ 10 ] = 0 dp[1][01]=0,dp[3][10]=0 dp[1][01]=0,dp[3][10]=0 d p dp dp 的第二维是集合的二进制表示, 01 01 01 表示 结点 1 1 1 是第一个终端点, 10 10 10 表示结点 3 3 3 是第二个终端点。)。

强行连通

2 , 3 , 4 2,3,4 2,3,4 d p [ 1 ] [ 01 ] dp[1][01] dp[1][01] 代表的子树连通:

d p [ 2 ] [ 01 ] = 3 , d p [ 3 ] [ 01 ] = 1 , d p [ 4 ] [ 01 ] = 3 dp[2][01]=3,dp[3][01]=1,dp[4][01]=3 dp[2][01]=3,dp[3][01]=1,dp[4][01]=3

1 , 2 , 4 1,2,4 1,2,4 d p [ 3 ] [ 10 ] dp[3][10] dp[3][10] 代表的子树连通:

d p [ 1 ] [ 10 ] = 1 , d p [ 2 ] [ 10 ] = 2 , d p [ 4 ] [ 10 ] = 4 dp[1][10]=1,dp[2][10]=2,dp[4][10]=4 dp[1][10]=1,dp[2][10]=2,dp[4][10]=4

现在所有结点都连通了包含 t e r m i n a l [ i ] terminal[i] terminal[i] 的子树,且包含的集合大小为 1 1 1

合并子树

接下来对于每个结点 v v v ,我们将两棵以 v v v 为根,包含大小为 1 1 1 的集合的子树,合并为一棵 以 v v v 为根,包含大小为 2 2 2 的集合的树。
d p [ v ] [ 11 ] = d p [ v ] [ 10 ] + d p [ v ] [ 01 ] (2) dp[v][11]=dp[v][10]+dp[v][01]\tag{2} dp[v][11]=dp[v][10]+dp[v][01](2)
在这个例子中, S S S 集合大小为 2 2 2,所以此时已经做完了。不妨更普遍一些, ∣ S ∣ > 2 |S|>2 S>2 显然我们要遍历所有的子集合,设 S S S 的子集合为 S ′ S' S,则有:
d p [ v ] [ S ] = m i n ∀ S ′ ⊆ S ( d p [ v ] [ S ] , d p [ v ] [ S ′ ] + d p [ v ] [ S − S ′ ] ) (2’) dp[v][S]=\underset{\forall S'\subseteq S}{min}(dp[v][S],dp[v][S']+dp[v][S-S'])\tag{2'} dp[v][S]=SSmin(dp[v][S],dp[v][S]+dp[v][SS])(2’)
那么我们可以将 ( 1 ) (1) (1) 式的强行连通也写的更具有普遍性(这样可以求出真正的 d p [ v ] [ S ] dp[v][S] dp[v][S]):
d p [ v ] [ S ] = m i n ∀ u ∈ V { d p [ u ] [ S ] + d ( u , v ) } (1’) dp[v][S]=\underset{\forall u\in V}{min}\{dp[u][S]+d(u,v)\}\tag{1'} dp[v][S]=uVmin{dp[u][S]+d(u,v)}(1’)
所以, d p dp dp 过程就是不断的进行三个操作:“强行连通”“合并子树““强行连通”

稍等…为什么合并子树之后还需要进行强行连通?

举个简单的例子: S = 2 S=2 S=2,对于任意一个结点 v v v 而言,合并子树只是令 d p [ v ] [ 11 ] = d p [ v ] [ 01 ] + d p [ v ] [ 10 ] dp[v][11]=dp[v][01]+dp[v][10] dp[v][11]=dp[v][01]+dp[v][10]。但不代表合并之后 d p [ v ] [ 01 ] + d p [ v ] [ 10 ] dp[v][01]+dp[v][10] dp[v][01]+dp[v][10] 就是真正的一棵以 v v v 为根且包含 S S S 的最小边权和子树。

在这里插入图片描述

假设 v = 1 v=1 v=1 S = { 3 , 4 } S=\{3,4\} S={3,4},此时经过一次强行连通之后 d p [ 1 ] [ 01 ] = a + c , d p [ 1 ] [ 10 ] = a + b dp[1][01]=a+c,dp[1][10]=a+b dp[1][01]=a+c,dp[1][10]=a+b,合并后 d p [ 1 ] [ 11 ] = 2 a + b + c dp[1][11]=2a+b+c dp[1][11]=2a+b+c。但实际上 d p [ 1 ] [ 11 ] = d p [ 2 ] [ 11 ] + d ( 1 , 2 ) = a + b + c dp[1][11]=dp[2][11]+d(1,2)=a+b+c dp[1][11]=dp[2][11]+d(1,2)=a+b+c。这也就是我们需要再进行一次强行连通的目的。因为进行完了一次强行连通,我们会对所有的包含集合大小相同的以 v v v 为根的树求出其真正的最小边权和。

这就是为什么我们要在合并子树之后再进行一次强行连通。

但其实,如果我们循环进行两次操作:“合并子树”“强行连通” 也是没问题的。因为合并子树的目的是 “扩大集合”,前提是子树所代表的最小边权和准确。因为初始时只有 d p [ t e r m i n a l [ i ] ] [ 1 < < ( i − 1 ) ] dp[terminal[i]][1<<(i-1)] dp[terminal[i]][1<<(i1)],如果此时我们进行合并子树的话,是不会有任何反应的,因为没有进行连通,所以一个点只对应一个集合。然后再进行强行连通操作。所以每次只要执行两次操作就行了,更省事。

d p dp dp 过程就是不断的进行两个操作:“合并子树”“强行连通”

到这里,我们可以用一个基础版的代码求出给定点集 S S S 的最小斯坦纳树。基础版的时间复杂度是 O ( 3 k N + N 2 l o g M + 2 k N 2 ) O(3^kN+N^2logM+2^kN^2) O(3kN+N2logM+2kN2)。因为我们需要用到最短路,所以考虑用 n n n d i j k s t r a dijkstra dijkstra 求解出任意两点之间的最短路(当且仅当不含负权边,且 N N N 数量级较小时用)。

合并子树:遍历 S S S 的子集 m a s k mask mask + 遍历每个点 + 枚举 m a s k mask mask 的子集 = O ( 3 k N ) O(3^kN) O(3kN)

强行连通:遍历 S S S 的子集 m a s k mask mask + 遍历最终根结点 u u u 和中转根结点 v v v = O ( 2 k N 2 ) O(2^kN^2) O(2kN2)

预处理最短路 n n n D i j k s t r a Dijkstra Dijkstra = O ( N 2 l o g M ) O(N^2logM) O(N2logM)

for (int mask = 1; mask < (1 << k); mask ++) {
    
    for (int submask = mask; submask; submask = (submask - 1) & mask)
        for (int u = 0; u < N; u ++)
            dp[mask][u] = min(dp[mask][u], dp[submask][u] + dp[mask ^ submask][u]);

    for (int u = 0; u < N; u ++) {
        for (int v = 0; v < N; v ++)
            dp[mask][v] = min(dp[mask][v], dp[mask][u] + d[u][v]);
         
}
【至臻版本】

代码来自 J i a n g l y Jiangly Jiangly(进行了一些个人习惯上的修改)。

for (int s = 0; s < (1 << (K - 1)); s++) {
        for (int t = s; t != 0; t = (t - 1) & s) 
            for (int i = 0; i < N; i++) 
                dp[s][i] = min(dp[s][i], dp[t][i] + dp[s ^ t][i]);
     
        priority_queue<PII, vector<PII>, greater<PII>> pq;
        vector<bool> vis(N, false);
     
        for (int i = 0; i < N; i++) 
            if (dp[s][i] != inf) 
                pq.push({dp[s][i], i});

        while (!pq.empty()) {
            auto [d, x] = pq.top();
            pq.pop();
            
            if (vis[x]) continue;
            vis[x] = true;
             
            for (auto [y, w] : adj[x]) {
                if (dp[s][y] > dp[s][x] + w) {
                    dp[s][y] = dp[s][x] + w;
                    pq.push({dp[s][y], y});
                }
            }
        }
    }

至臻版本和普通版本的区别就是,至臻版本没有提前预处理出任意两结点之间的最短路。而是在需要用到最短路的时候调用 D i j k s t r a Dijkstra Dijkstra

我们将有效值统统插入最小优先队列中,然后进行 D i j k s t r a Dijkstra Dijkstra d p [ S ] [ i ] dp[S][i] dp[S][i] 会在 D i j k s t r a Dijkstra Dijkstra 中松弛为最小值。

【问题】
for (int i = 0; i < N; i++) 
    if (dp[s][i] != inf) 
        pq.push({dp[s][i], i});

这段代码为啥和普通的 D i j k s t r a Dijkstra Dijkstra 不一样?正常的 D i j k s t r a Dijkstra Dijkstra 是令起点为 0 0 0,并且只有一个起点。

这段代码的意思是什么?

往最小堆中插入多个值,其中最小的会在第一次被拿出来。此时最小的值对应的根节点可以看作起点。假设这个点为 u u u,然后用这个起点的值 d p [ s ] [ u ] dp[s][u] dp[s][u],去更新起点周围的点,设为 v v v。此时如果 d p [ s ] [ v ] dp[s][v] dp[s][v] 大于 d p [ s ] [ u ] + w ( u , v ) dp[s][u]+w(u,v) dp[s][u]+w(u,v),那么说明 v v v u u u 转移过来更优(强行连通操作之前, v v v 的合并只会从 v v v 的子树中转移,详细见合并操作)。那么 v v v 就会被松弛成功。更新完 u u u 周围的点,我们重新从最小堆中拿出最小的点作为起点去松弛周围的点。

为什么这样做是对的?

性质 2 2 2】每次从最小堆中取出来的值,设其对应根结点是 u u u,显然 u u u 一定从最优的点转移过来的。

证明】因为进行 D i j k s t r a Dijkstra Dijkstra 的元素一定是同集合大小的,假设 d p [ s ] [ u ] dp[s][u] dp[s][u] 是第一个被取出来的最小值,若它不是最优解,假设 u u u 真正的最优解是 ρ ( s , u ) \rho(s,u) ρ(s,u),且设它是由 v v v 转移而来的,显然有 ρ ( s , u ) = d p [ s ] [ v ] + d ( u , v ) ≤ d p [ s ] [ u ] \rho(s,u)=dp[s][v]+d(u,v) \leq dp[s][u] ρ(s,u)=dp[s][v]+d(u,v)dp[s][u]。也就是说存在 d p [ s ] [ v ] < d p [ s ] [ u ] dp[s][v]<dp[s][u] dp[s][v]<dp[s][u],所以 d p [ s ] [ u ] dp[s][u] dp[s][u] 不是第一个被取出来的最小值,这与假设矛盾。所以第一个被取出的最小值一定是最优解。

假设 d p [ s ] [ v ] dp[s][v] dp[s][v] 是第 t t t 次被取出来的最小值,如果此时 d p [ s ] [ v ] = ρ ( s , v ) dp[s][v]=\rho(s,v) dp[s][v]=ρ(s,v),说明接下来它不会被后续的点再更新。

设后续的点为 x x x,如果 x x x 能更新 v v v,说明 d p [ s ] [ v ] > d p [ s ] [ x ] + d ( v , x ) dp[s][v]>dp[s][x]+d(v,x) dp[s][v]>dp[s][x]+d(v,x),又因为后续出现的点 d p [ s ] [ x ] > d p [ s ] [ v ] dp[s][x]>dp[s][v] dp[s][x]>dp[s][v],这与假设矛盾,所以 x x x 必然是 v v v 或者 v v v 之前出现过的点即前 t − 1 t-1 t1 个点。所以每次从最小堆中取出来的值,一定是最优解。

性质 2 2 2 证毕。

根据性质 2 2 2,我们可以知道当 D i j k s t r a Dijkstra Dijkstra 结束之后, ∀ i ∈ V , d p [ s ] [ i ] = ρ ( s , i ) \forall i\in V,dp[s][i]=\rho(s,i) iV,dp[s][i]=ρ(s,i)。所以算法正确。

p s ps ps:现在是凌晨四点半,11点的时候喝了一杯咖啡,然后奋战到现在,妈呀真的战斗了个痛快。为什么写这一篇文章,主要是因为我实在看不懂网上的题解,有好多地方不清楚,然后又没有人细讲,所以干脆自己写了一篇,供大家参考。当然斯坦纳树还有许多的应用,我后续再补充,应该不会有第一次学这么难。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

One-hot编码和Multiple-hot编码

在推荐系统和机器学习中&#xff0c;我们通常会遇到两种类型的编码方式&#xff1a;One-hot 编码和 Multiple-hot 编码&#xff08;有时也称为 Multi-hot 编码&#xff09;。这两种编码方式用于将分类数据转换为数值表示&#xff0c;以便机器学习模型能够处理这些数据。 1、On…

国产开源大模型都有哪些?

随着ChatGPT引领的大模型热潮&#xff0c;国内的公司开始相继投入研发自己的人工智能大模型&#xff0c;截止到2023年10月&#xff0c;国产公司的大模型有近百个&#xff0c;包括一些通用大模型&#xff0c;比如百度的文心一言&#xff0c;也有特定领域的专用大模型&#xff0c…

电力时代的液冷-EAK水冷电阻器的来源

当电气设备出现故障时&#xff0c;我们经常会表述成“这个东西烧了”。为什么用“烧”而不是“破”了或“坏”了呢?因为在电气产品中&#xff0c;一部分的电能会在使用的过程中通过电阻和电感的作用转化为热&#xff0c;如果因为设计或故障原因&#xff0c;产生的热没有被有效…

python自动化笔记:os模块和异常处理

目录 一、os模块1.1、常用方法1.2、其他方法&#xff08;了解即可&#xff09; 二、异常处理 try except2.1、语法格式1&#xff1a;2.2、语法格式2&#xff1a;指定异常类别&#xff0c;捕获异常2.3、语法格式3&#xff1a;try-finally 语句无论是否发生异常都将执行最后的代码…

〖基础篇1〗ROS2 Foxy Ubuntu 20.04 (Focal Fossa)安装教程

目录 一、linux Ubuntu 20.04 (Focal Fossa)安装二、linux VPN安装三、linux anaconda安装&#xff08;可选&#xff09;四、linux ROS2 foxy安装1. 设置语言环境2. 设置DEB软件源3. 安装开发工具和依赖4. 安装ROS2 foxy桌面版本5. 运行示例 一、linux Ubuntu 20.04 (Focal Fos…

常见框架漏洞详解③!!

Apache Apache 是世界使⽤排名第⼀的 Web 服务器软件。它可以运⾏在⼏乎所有⼴泛使⽤的计算 机平台上&#xff0c;由于其跨平台和安全性被⼴泛使⽤&#xff0c;是最流⾏的 Web 服务器端软件之⼀。 apache⽬录结构&#xff1a; bin&#xff1a;存放常⽤命令⼯具&#xff0c;如h…

颠倒字符串中的单词(LeetCode)

题目 给你一个字符串 &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意&#xff1a;输入字符串 中可能会存在前导空格、尾随…

CSDN机器人与僵shi粉测试(真人看看)

​哈哈哈一起玩个游戏 发现老是莫名其妙有很多关注点赞与收藏&#xff0c;关注的几百个人应该都是机器人 此博文用于检测平台机器人阅读量 —>如果是真人请务必随便留言<— 可以根据阅读量与评论判断机器人数量 不用点赞收藏有机器人就行 机器人统一特征是在2019年左右…

【C++ 面试 - 基础题】每日 3 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

ImportError: DLL load failed while importing _rust: 找不到指定的程序的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

网络协议四 物理层,数据链路层

从这一节开始学习 五层模型。学习方法是从最底层物理层开始学习 七层模型 五层模型 各个层用的协议&#xff0c;以及加上协议后的称谓 各个层的作用 应用层&#xff1a;可以认为是原始数据&#xff0c;该数据称为 报文&#xff0c;用户数据。 运输层&#xff1a;也叫传输层&am…

【Linux】1w字详解自定义Shell管道 | 构建简易进程池

目录 续&#xff1a;通信 4 种情况 应用场景 1. 自定义 shell 管道 1. 包含头文件 2. 解析命令函数 详细步骤 3. 执行命令函数 4. 主函数 总结 2. 使用管道实现一个简易版本的进程池 代码结构 代码实现 channel.hpp tasks.hpp main.cc 子进程读取任务&#xff…

Stable Diffusion绘画 | 提示词基础原理

提示词之间使用英文逗号“,”分割 例如&#xff1a;1girl,black long hair, sitting in office 提示词之间允许换行 但换行时&#xff0c;记得在结尾添加英文逗号“,”来进行区分 权重默认为1&#xff0c;越靠前权重越高 每个提示词自身的权重默认值为1&#xff0c;但越靠…

Al+CRM:企业增长新引擎

在企业中&#xff0c;GenAI可以帮助改进决策制定、优化运营流程、增强产品和服务的创新能力&#xff0c;以及提供更加个性化的客户体验&#xff0c;在Gartner的调研中&#xff0c;AI将在以下领域发挥重要作用。 AICRM,将改变原有CRM的使用体验。把抽屉式的系统操作&#xff0c;…

【ubuntu20.04 运行sudo apt-get upgrade报错】

ubuntu20.04 运行sudo apt-get upgrade报错 1 确保系统是最新的2 检查你的软件源列表是否正确无误3 修改软件源3.1 备份原来的源3.2 更换源3.2.1 Ubuntu20.04(focal)阿里云软件源3.2.2 Ubuntu20.04清华软件源 3.3 更新软件源 4 修复升级5 重新安装特定软件包6 导入缺失的密钥 1…

7.1.算法分析与设计-算法分析基本概念与算法分析基础

很难 算法基础知识 算法是对特定问题求解步骤的一种描述&#xff0c;它是指令的有限序列&#xff0c;其中每一条指令表示一个或多个操作。简单的说算法就是某个问题的解题思路&#xff0c;算法的五个重要特性如下&#xff1a; 有穷性。一个算法必须总是&#xff08;对任何合…

数据结构——优先队列

文章目录 一、基本介绍二、基本操作三、实现1 实现的思路2 大顶堆实现2.1 概念2.2 完全二叉树的实现方式2.3 优先队列的图示2.4 对于基本操作实现的讲解2.4.1 检查队列是否为空 ( isEmpty )2.4.2 检查队列是否已满 ( isFull )2.4.3 查看 ( peek )2.4.4 插入 ( offer )2.4.5 删除…

计算数学精解【5】-prolog计算精解(1)

文章目录 概述什么是prolog安装 基础控制台增加规则参考文献 概述 什么是prolog Prolog&#xff08;Programming in Logic&#xff09;是一种面向演绎推理的逻辑型程序设计语言&#xff0c;最早于1972年由柯尔麦伦纳&#xff08;Colmeraner&#xff09;及其研究小组在法国马赛…

Python教程(十三):常用内置模块详解

目录 专栏列表1. os 模块2. sys 模块3. re 模块4. json 模块5. datetime 模块6. math 模块7. random 模块8. collections 模块9. itertools 模块10. threading 模块 总结 专栏列表 Python教程&#xff08;十&#xff09;&#xff1a;面向对象编程&#xff08;OOP&#xff09;P…

uniapp h5本地预览pdf教程 (含白屏|跨域解决方案)

第一步 下载pdf.js 很多pdf.js版本在真机ios环境都会白屏 经测试后2.5.207版本比较稳定&#xff0c;Android和IOS环境PDF文件都能加载成功 下载地址 https://github.com/mozilla/pdf.js/releases/tag/v2.5.207https://github.com/mozilla/pdf.js/releases/tag/v2.5.207第二步 解…