Amortized analysis平摊分析和Competitve analysis竞争分析的关系

一、Amortized analysis平摊分析

1.1 引言

在计算机科学中,特别是算法分析中,平摊分析寻找在最坏情况下的操作序列中每操作的平均耗费时间。平摊分析只保证最坏情况性能的每操作耗费时间,不涉及平均情况性能。

这个方法需要知道操作序列中可能发生的每个操作。通常应用在操作间存在状态的数据结构中。基本思想是一个最坏情况操作会改变状态从而不会在一段时间内再次出现,因此"平摊"它的耗费

均摊分析与平均情况分析的区别在于,平均情况分析是平均所有的输入,比如,INSERTION SORT算法对于所有可能的输入在平均情况下表现性能不错就算它在某些输入下表现性能是非常差的。而均摊分析是平均操作,比如,TABLEINSERTION算法在所有的操作上平均表现性能很好尽管一些操作非常耗时。在均摊分析中,不涉及概率,并且保证在最坏情况下每一个操作的平均性能。

有三类比较常见的均摊分析:

1.聚类分析:证明对所有的n,由n个操作所构成的序列的总时间在最坏情况下为T(n),每一个操作的平均成本为T(n)/n;比如栈的操作,对于一个空栈的入栈和出栈的操作

2.记账方法:在平摊分析的记帐方法中,决定每一个操作的均摊成本,对不同的操作赋予不同的费用,某些操作的费用比它们的实际代价或多或少。我们对一个操作的收费的数量称为平摊代价。当一个操作的平摊代价超过了它的实际代价时,两者的差值就被当作存款(credit),并赋予数据结构中的一些特定对象,可以用来补偿那些平摊代价低于其实际代价的操作。这种方法与聚集分析不同的是,对后者,所有操作都具有相同的平摊代价。数据结构中存储的总存款等于总的平摊代价和总的实际代价之差。注意:总存款不能是负的。在开始阶段对于过度要价存储预先支付的存款,在后面的序列中再支付操作。比如,二进制计数器: 通过二进制触发器计算一系列数字。

3.势能方法:在平摊分析中,势能方法(potential method)不是将已预付的工作作为存在数据结构特定对象中存款来表示,而是将存款总体上表示成一种“势能”或“势”,它在需要时可以释放出来,以支付后面的操作。势是与整个数据结构而不是其中的个别对象发生联系的。比如,动态表,可以动态改变大小的连续存储数组。

接下来分别举例这3种方法。

1.2 聚类分析 aggregate method

1.2.1 栈

比如,有MULTIPOP操作的栈。有两种基本的栈操作都分别花费O(1)的时间: PUSH(S,x)和POP(S)分别是将对象x压入栈中,从栈S的顶部弹出并返回弹出的对象。将每一个操作的花销都赋为1. 一连串n个PUSH和POP操作的总消耗为n,对于n个操作的实际运行时间为O(n).

现在添加一个额外的栈操作MULTIPOP。MULTIPOP(S,k) 是弹出栈S的前k个对象(或者弹出整个栈如果k大于栈的大小的话)。MULTIPOP的总消耗是min{|S|,k}.

下面为一个例子(倒数第一列里的3错了):
在这里插入图片描述
粗略地分析,MULTIPOP (S,k)将会花费O(n)的时间,因此,在这里插入图片描述
在操作序列中,一些操作可能会很廉价,但是一些操作可能会非常昂贵耗时,比如MULTIPOP(S,k). 然而,最坏的操作往往不是经常被调用的。因此,传统的最坏的单一操作分析会给出过于消极的边界。

我们的目标是,对于每一个操作,我们希望能够赋予其一个均摊的成本 C i ^ \hat{C_i} Ci^来对实际的总的成本进行定界。对于n个操作的任意序列,我们有在这里插入图片描述
这里, C i C_i Ci是表示第i步的实际成本。

使用聚类分析使得有更加紧凑的边界分析,对于所有的操作都有相同的均摊成本.在这里插入图片描述
观察得知,POP操作的数目一定小于或者等于PUSH操作的数目。因此,我们可以得到:

在这里插入图片描述

因此,平均来看,MULTIPOP(S,k)这一步将花费O(1)而不是O(k)的时间。

1.2.2 计数器

这里来看另一个例子,考虑一个从0开始计数的k位的二进制计数器。使用位的数组A[0,…, k-1]来记录计数。存储在计数器中的二进制数在A[0]有最低阶的位,在A[k-1]有最高阶的位,并且有
在这里插入图片描述
上式即是数的二进制表示

初始时,x=0, 对于i = 0,… k-1, 都有A[i]=0

一个存储案例如下:
在这里插入图片描述

我们可以看到,每次增加1,改变位置的数目就是cost。那么粗略计算,我们可以得到T(n)<= kn,因为一个增加操作可能会改变所有的k位。

我们使用聚类计数来紧凑分析的话,有基本的操作flip(1->0)flip(0->1)

在n个INCREMENT操作的一个序列中,

A[0] 每一次INCREMENT被调用的时候都会flip,因此flip n次;(通过列中标记的黄色可以看出来规律)

A[1] 每两次调用INCREMENT时flip,因此flip n/2次;

A[i] flips 在这里插入图片描述
次.

因此,
在这里插入图片描述
每一个操作的均摊成本为: O(n)/n =O(1).

1.3 记账方法 accounting method

1.3.1 栈

记账方法的基本思路为,对于每一个有实际成本 C O P C_{OP} COP的操作OP而言,均摊成本 C O P ^ \hat{C_{OP}} COP^被分配使得对于n个操作的任意序列,有在这里插入图片描述

如果 C O P ^ > C O P \hat{C_{OP}}>C_{OP} COP^COP,那么额外的部分就可以被存储为预付的存款(credit),这笔存款可以在之后对于 C O P ^ < C O P \hat{C_{OP}}<C_{OP} COP^COP的操作时被用。这样的要求实质上是使得存款不会为负。

我们回到有MULTIPOP操作的栈的问题,对于这样的栈,将均摊成本分配为:
在这里插入图片描述
其中,credit是栈中条目的数目。

从一个空栈开始,n1个PUSH,n2个POP和n3个MULTIPOP操作的任意序列最多的花销是在这里插入图片描述
,这里,n = n1 + n2 + n3.

需要注意的是,当有超过一种类型的操作时,每一种类型的操作可能被赋予不同的均摊成本

下面通过一个银行家的观点来看记账方法。假如你正在租一个操作硬币的机器,并且根据操作的数量来收费。那么有两种支付方法:

A. 对每一种实际的操作支付实际费用:比如PUSH支付1元,POP支付1元,MULTIPOP支付k元

B. 开一个账户,对每一个操作支付平均费用:比如PUSH支付2元,POP支付0元,MULTIPOP支付0元

如果平均花销大于实际的费用,那么额外的将被存储为credit(存款);如果平均成本小于实际的花费,那么credit将被用来支付实际的花费。这里的限制条件为:

对任意的n个操作,在这里插入图片描述
,也就是说,要保证在你的账户中有足够的存款。

下面是一个例子:

在这里插入图片描述

1.3.2 计数器

对于之前的二进制计数器有一样的道理,赋予均摊成本为:
在这里插入图片描述

我们可以观察到flip(0->1)的数目大于等于flip(1->0),因此有

在这里插入图片描述

1.4 势能方法potential method

势能方法是从一个物理学家的角度出发看问题,基本思路是有势,对于每一个操作OP直接设置 C O P ^ \hat{C_{OP}} COP^不是那么简单。因此,我们定义一个势能函数作为桥梁,也就是,我们将一个值赋给一个状态而不是赋给一个操作,这样,均摊成本就是基于势能函数来计算的。

定义势能函数为在这里插入图片描述
: 其中S是状态集合。

均摊成本的设置为:在这里插入图片描述
,因此我们有
在这里插入图片描述
为了保证 在这里插入图片描述
,必须要确保在这里插入图片描述

1.4.1 栈

对于栈的例子,令在这里插入图片描述
表示栈中的条目的数目。实际上,我们可以简单讲存款作为势能。这里状态Si表示在第i个操作之后栈的状态。对于任意的i,有在这里插入图片描述
。因此,栈S的状态为:

在这里插入图片描述
那么势能函数 的折线图表示为下图
在这里插入图片描述
我们如下定义:
在这里插入图片描述
因此,从一个空栈开始,n1个PUSH,n2个POP和n3个MULTIPOP操作的任意序列花费最多在这里插入图片描述

,这里n = n1 + n2 + n3.

1.4.2 计数器

在二进制计数器中,在计数器中将在这里插入图片描述
设置为势能函数,即1的个数:

在这里插入图片描述
在计数器中将在这里插入图片描述
设置为势能函数,在第i步,flips Ci的数目为:
在这里插入图片描述
其中第一行中,我们从上图中可以观察到,每一行值有且只有1个0变为1,所以解释了why.
第二行的话,根据定义,也可以直接看出来。

因此,我们有
在这里插入图片描述
换句话说,从00…0开始,n个INCREMENT操作的一个序列最多花费2n时间。

1.5 动态表

1.5.1 聚类分析

假设现在我们被要求开发一个C++的编译器。Vector是一个C++的类模板来存储一系列的对象。它支持一下操作:

a.push_back: 添加一个新的对象到末尾

b.pop-back:将最后一个对象弹出

注意vector使用一个连续的内存区域来存储对象。那么我们该如何为vector设计一个有效的内存分配策略呢?

这就引出了动态表的问题。

在许多应用中,我们不能够提前知道在一个表中要存储多少个对象。因此,我们不得不对一个表分配一定空间,但最后发现其实不够用。下面引出两个概念:

动态扩展:当在一个全表中插入一个新的项时,这个表必须被重新成一个更大的表,原来表中的对象必须被拷贝到新表中。

动态收缩:相似的,如果从一个表中删除了许多的对象,那么这个表可以被重新分配成一个尺寸变小的新表

动态表扩展的例子:
在这里插入图片描述
接下来,我们将给出一个内存分配策略使得插入和删除的均摊成本是O(1),即便一个操作触发扩展或者收缩时其实际成本是较大的

在这里插入图片描述
考虑从一个空栈开始的操作的一个序列
在这里插入图片描述
Overflow之后扩展表的操作:
在这里插入图片描述
粗略地分析,考虑这样的一个操作序列,如果我们根据基本的插入和删除操作来定义成本,那么第i个操作的实际成本 C i C_i Ci
在这里插入图片描述
这里的 C i = i C_i = i Ci=i是当表为满的时候,因为此时我们需要插入一次,并且拷贝i-1项到新表中。

如果n个操作被执行了,那么一个操作的最坏情况下的成本将为O(n). 这样的话,对于总的n个操作的总运行时间为O(n^2),并不如我们需要的紧凑。

特别的,表扩展发生在第i次操作,其中i-1恰好是2的幂。
在这里插入图片描述
因此,我们可以将Ci分解为:
在这里插入图片描述
这样n个操作的总花费为:
在这里插入图片描述
因此,每一个操作的均摊成本为3,换句话说,每一个TABLEINSERT操作的平均成本为O(n)/n=O(1)

1.5.2 记账分析

如果我们使用记账方法:

对于第i次操作,一个均摊成本在这里插入图片描述
被支出。这个费用被消耗到运行后面的操作。任何不是立即被消耗掉的数量将被存在一个“银行”用于之后的操作。

因此,对于第i个操作,$3被用在以下场合:

A.$1支付自身插入操作

B.$2存储为之后的表扩展

换句话说,均摊成本的和给出了实际成本的和的一个上界。

在这里插入图片描述

1.5.3 势能分析

如果我们使用势能方法:银行账户可以被看做一个动态集合的势能函数。

我们定义势能函数

在这里插入图片描述

其折线图为:

在这里插入图片描述
初始时 Φ 0 = 0 \Phi_0=0 Φ0=0, 并且非常容易验证当表总是至少半满的时候有 Φ i ≥ Φ 0 \Phi_i\ge\Phi_0 ΦiΦ0 。那么关于 Φ \Phi Φ的成本 C i ^ \hat{C_i} Ci^被定义为:

在这里插入图片描述

这样的话, 在这里插入图片描述
就是实际操作的一个上界了。

下面分 Φ \Phi Φ的两种情况来计算 C i ^ \hat{C_i} Ci^

Case-1:第i次插入不会触发一个扩展

此时,在这里插入图片描述
, 这里,numi表示第i次操作之后表项的数目,sizei表示表的大小,Ti表示势能。

在这里插入图片描述
Case-2:第i次操作触发了一个表的扩展,此时
在这里插入图片描述
在这里插入图片描述
因此,从一个空表开始,一个n个TABLEINSERT操作的序列在最坏情况下花费O(n).

删除操作是类似的分析。

1.6 记账方法和势能方法的关系

在这里插入图片描述

二、势能法用于竞争分析

我们再回顾一下势能法:

势能方法将会计方法中的“银行账户”定义为势能。要解释清楚这种方法先做以下假设。

  • 从数据结构 D 0 D_0 D0开始,操作 i i i D i − 1 D_{i−1} Di1转化为 D i D_i Di

  • 每次操作的实际成本是 C i C_i Ci

  • 定义势能函数 Φ , Φ : { D i } → I  ⁣ R \Phi,\Phi : \{ D_i \} \xrightarrow{}{\rm I\!R} Φ,Φ:{Di} IR

  • Φ ( D 0 ) = 0 \Phi(D_0)=0 Φ(D0)=0

  • Φ ( D i ) ≥ 0 \Phi(D_i)\geq0 Φ(Di)0

那么平摊成本可以如下表示:

C ^ i = C i + Φ ( D i ) − Φ ( D i − 1 ) \widehat C_i = C_i+\Phi(D_i)-\Phi(D_{i-1}) C i=Ci+Φ(Di)Φ(Di1)

如果 Δ Φ i > 0 \Delta \Phi_i > 0 ΔΦi>0表示平摊成本过多,操作 i i i将额外的能量存储在数据结构 D i D_i Di中,用于之后的操作;如果 Δ Φ i < 0 \Delta \Phi_i <0 ΔΦi<0表示平摊成本不足,那么就把数据结构 D i − 1 D_{i−1} Di1中的能量拿出来用于操作。这样就可以通过平摊成本去估计算法实际操作的成本。方法的可行性描述如下。
∑ i = 1 n C ^ i = ∑ i = 1 n [ Φ ( D i ) − Φ ( D i − 1 ) + C i ] = ∑ i = 1 n C i + Φ ( D n ) − Φ ( D 0 ) ≥ ∑ i = 1 n C i \sum_{i=1}^{n}\widehat C_i = \sum_{i=1}^{n} [\Phi(D_i)-\Phi(D_{i-1})+C_i]=\sum_{i=1}^{n}C_i+\Phi(D_n)-\Phi(D_0) \geq\sum_{i=1}^{n}C_i i=1nC i=i=1n[Φ(Di)Φ(Di1)+Ci]=i=1nCi+Φ(Dn)Φ(D0)i=1nCi

所以这个方法的关键是想办法找到问题对应的 Φ \Phi Φ。对于动态表它的 Φ ( D i ) = 2 i − 2 ⌈ l g i ⌉ \Phi(D_i)=2i-2^{\lceil lg i \rceil} Φ(Di)=2i2lgi,这个不容易想到,所以对于动态表问题,用前两种平摊分析方法优越于势能方法。

2.1 自组织表(self-organizing lists)

但是,在竞争分析(Competitive analysis)中,势能方法体现了强大而优美的作用,这里面以自组织表(self-organizing lists)为例,详细解释这种强大的分析方法,自组织表有点像搜索引擎的索引。

本文讨论的自组织链表基于以下假设

  • 一个有 n n n个元素的表
  • 访问元素 x x x的成本是 r a n k ( x ) rank(x) rank(x),即 x x x到表头的距离。
  • 表可以通过相邻元素的移项进行重排,每次移项的成本是1。即 t r a n s p o s e ( x ) transpose(x) transpose(x):交换 x x x x x x的前驱,时间复杂度为 O ( 1 ) O(1) O(1)

在对表访问的时候,我们希望被访问的元素尽可能靠近表头,从而加快访问的速度,因此在每次访问某元素后都要对表格作一定的调整。那么怎样调整呢?为了方便的讨论不同方法调整的效果,下面引入两个定义。

  • 在线算法(online algorithm),每次操作仅提供操作序列S中的一个操作,在线算法无法利用未来的操作信息去改进操作过程,对于自组织表问题,也即无法根据未来的访问方式调整表,只能根据当前的访问改进表。
  • 离线算法(off-line algorithm),可以看到整个操作序列,从而根据每一次访问和已知的未来访问序列,产生最好的调整方法。

一个直觉的想法是,记录每个元素被访问的次数,从而使得表中的元素根据访问次数从高到低进行排序。但这样的操作似乎太费劲了。在实际引用中,一般是使用MTF策略,即每次访问一个元素 x x x,访问完了之后将它移动的到表头。它的成本是 2 r a n k ( x ) 2rank(x) 2rank(x)(访问它+前移它)。这种策略对局部性有很好的表现,因为通常访问都很集中。下面通过竞争分析说明这种方法的有效性,即证明:

前移启发式自组织表MTF是4竞争的。

证明:

假设 L i L_i Li是使用MTF方法,第 i i i次访问后的表, L i ∗ {L_i}^* Li表示使用OPT方法,第 i i i次访问后的表。

C i = 2 r a n k L i − 1 ( x ) C_i=2rank_{L_{i-1}}(x) Ci=2rankLi1(x)表示MTF方法在第 i i i次操作时候的成本; C i ∗ = r a n k L i − 1 ∗ ( x ) + t i {C_i}^*=rank_{{L_{i-1}}^*}(x)+t_i Ci=rankLi1(x)+ti表示OPT方法在第 i i i次操作时候的成本, t i t_i ti表示OPT方法移项的次数。下面定义针对这一问题的势能函数。

Φ ( L i ) = 2 ∣ ( x , y ) , x ≺ L i y   a n d   y ≻ L i ∗ x ∣ \Phi(L_i)=2|(x,y),x\prec _{L_i}y \ and \ y\succ _{{L_i}^*}x| Φ(Li)=2(x,y),xLiy and yLix

也就是 Φ ( L i ) = 2 i n v e r s i o n s \Phi(L_i) = 2inversions Φ(Li)=2inversions,即两倍的逆序对,之所以是两倍的逆序对是因为有利于后期的消项。这个势能函数满足势能函数的定义: Φ ( L 0 ) = 0 \Phi(L_0)=0 Φ(L0)=0, Φ ( L i ) ≥ 0 \Phi(L_i)\geq0 Φ(Li)0.

多解释一下,所谓的逆序关系是: L i L_i Li中一对元素 y y y z z z,在 L i L_i Li y y y z z z前面,在 L i ∗ {L_i}^* Li z z z y y y前面。例如 L i L_i Li中有元素 < e , c , a , d , b > <e,c,a,d,b> <e,c,a,d,b> L i ∗ {L_i}^* Li中有元素 < c , a , b , d , e > <c,a,b,d,e> <c,a,b,d,e>,那么 L i L_i Li有5个逆序对,即 ( ( e , c ) , ( e , a ) , ( e , d ) , ( e . b ) , ( d , b ) ) ((e,c),(e,a),(e,d),(e.b),(d,b)) ((e,c),(e,a),(e,d),(e.b),(d,b)), 因此 Φ ( L 0 ) = 0 \Phi(L_0)=0 Φ(L0)=0

根据定义,我们可以清楚知道:每一个转置操作对于 Φ \Phi Φ的改变是±2。

再引入以下四个集合的定义

在这里插入图片描述

那么元素 x x x L i − 1 L_{i-1} Li1 L i − 1 ∗ {L_{i-1}}^* Li1中的位置可以表示如下:

r = ∣ A ∣ + ∣ B ∣ + 1 r = |A| + |B| + 1 r=A+B+1

r ∗ = ∣ A ∣ + ∣ C ∣ + 1 r^* = |A| + |C| + 1 r=A+C+1

我们这里加个图,便于理解:
在这里插入图片描述

用MTF当请求访问 x x x的时候生成了|A|个逆序对,消除了|B|个逆序对。假设当用OPT请求访问 x x x的时候,进行了 t i t_i ti次移项,那么因为每次移项最多产生一个逆序对。所以
Δ Φ = Φ ( L i ) − Φ ( L i − 1 ) ≤ 2 ( ∣ A ∣ − ∣ B ∣ + t i ) \Delta \Phi =\Phi(L_i)-\Phi(L_{i-1}) \leq 2(|A|-|B|+t_i) ΔΦ=Φ(Li)Φ(Li1)2(AB+ti)

因此,继续分析
C ^ i = C i + Φ ( L i ) − Φ ( L i − 1 ) = 2 r + Δ Φ ≤ 2 ( ∣ A ∣ + ∣ B ∣ + 1 ) + 2 ( ∣ A ∣ − ∣ B ∣ + t i ) = 4 ∣ A ∣ + 2 + 2 t i ≤ 4 ( r ∗ − 1 ) + 2 − 2 t i = 4 ( r ∗ + t i ) − 2 ( 1 + t i ) ≤ 4 ( r ∗ + t i ) = 4 C i ∗ \widehat C_i = C_i + \Phi(L_i)-\Phi(L_{i-1}) \\ =2r+ \Delta \Phi \\ \qquad \qquad \qquad \qquad \qquad \qquad \qquad\leq 2(|A|+|B|+1)+2(|A|-|B|+t_i) \qquad \qquad \qquad\\ = 4|A|+2+2t_i \\\qquad\leq 4(r^*-1) + 2 - 2t_i \\\qquad= 4(r^* + t_i) - 2(1 + t_i) \\ \leq4(r^* + t_i) =4{C_i}^* C i=Ci+Φ(Li)Φ(Li1)=2r+ΔΦ2(A+B+1)+2(AB+ti)=4A+2+2ti4(r1)+22ti=4(r+ti)2(1+ti)4(r+ti)=4Ci

解释下上式,第2个小于等于是因为:由 r ∗ r^* r定义,则 r ∗ ≥ ∣ A ∣ + 1 r^*\ge|A|+1 rA+1;第3个小于等于是因为: 2 ( 1 + t i ) > 0 2(1 + t_i) >0 2(1+ti)>0.

因此:
C M T F ( S ) = ∑ i = 1 n C i = ∑ i = 1 n ( C ^ i − Φ ( D i ) + Φ ( D i − 1 ) ) ≤ ∑ i = 1 n C ^ i + Φ ( L 0 ) − Φ ( L i ) ≤ 4 C O P T ( S ) C_{MTF}(S)=\sum_{i=1}^{n}C_i=\sum_{i=1}^{n}(\widehat C_i - \Phi(D_i)+\Phi(D_{i-1}) ) \\ \leq \sum_{i=1}^{n} \widehat C_i +\Phi(L_0)-\Phi(L_i) \\ \leq 4C_{OPT}(S) CMTF(S)=i=1nCi=i=1n(C iΦ(Di)+Φ(Di1))i=1nC i+Φ(L0)Φ(Li)4COPT(S)

根据竞争分析的定义,从而证明出MTF是4竞争。

总结:势能方法应用的关键是定义一个好的势能函数,而势能函数的定义又跟问题本身密切相关,本文中的关键应该是用逆序对去描述两个表的差异,从而引出势能函数,然后顺利的用集合去描述元素 x x x的位置。

2.2 MTF是2-competitive

如果我们定义,元素 x x x移到表头是常数时间(即无cost),在这种情况下,可以证明MTF是2-competitive。

详细可以参考问下【6】的定理2.1,过程写的很清楚。

这里有几个定义需要说明下,原文:

After a F I N D FIND FIND operation on an element x x x, an algorithm may move x x x forward — this operation is called a free transposition. Alternatively, an algorithm may transpose two adjacent items in the list for cost 1, which is called a paid transposition

在这里引出了2个概念,所谓的free transposition就是 x x x向表头前移了若干步,根据定义,这是没有cost的;所谓的paid transposition就是2个相近的元素交互,这时候不一定向前,这时候有1的cost。

在定理2.1利用潜能函数分析竞争的时候,根本没有分析OPT算法具体是什么,而是构造了一系列不等式,将分摊损失和最优离线损失关联起来,这是一个思考的思路。

注:还有一个例子用平摊分析来分析LRU是k-competitive的,详见参考文献【8】.

参考文献

【1】Amortized analysis平摊分析——记算法导论视频总结

【2】算法课笔记系列(七)—— 平摊分析Amortized Analysis

【3】如何理解算法平摊分析中的势能方法(Potential Method)?

【4】《算法导论》:竞争性分析、自组织表

【5】Competitive Analysis

【6】MT F, BIT, and COMB: A Guide to Deterministic and Randomized Online Algorithms for the List Access Problem

【7】Introduction to Algorithms

【8】COT 6405: Analysis of Algorithms

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值