线段树入门+例题详解

线段树是非常经典的树形数据结构,其在ACM中也是经常出现的,下面对线段树进行说明并就相关例题展开。



问题与解决方法

如果我们有 n(n>1e5) 个数组成的数组,我们有 q(q>1e5) 查询,每次查询 [li,ri] 范围内的数之和,可以使用前缀和来解决,这样每次查询的时间复杂度都是O(1)。

还是有 n(n>1e5) 个数组成的数组,我们有 u(u>1e5) 次修改,每次修改 [li,ri] 范围内的数,每个数加上一个 numberi,问 u 次修改后某个数是多少,可以使用差分来解决,这样每次修改的时间复杂度都是O(1)。

单点修改区间查询

现在有一个问题,还是有一个由 n(n>1e5) 个数组成的数组,我们有 q(q>1e5) 操作,但每次操作操作有两种可能:

  • 一是对数组中的一个数的值进行修改
  • 二是查询 [li,ri] 范围的数之和

这样用之前前缀和的方法最大时间复杂度为 O(nq),因为每次修改的时间复杂度都是 O(n)。

而线段树则可以在可接受的时间复杂度内解决这个问题,下面为其树形结构图:

这里面每个 mid 表示 [l,r] 的中间值,即 (l+r)/2,比如 mid1=(l+r)/2,mid2=(1+mid1)/2…,每个区间每次分为两个均匀的区间,直到每个区间只有一个数,这里的对于每个树中的 [l,r] 维护这个范围的数之和。

这个树形结构的树深度为 log2(n),那么每次对数组中的一个数的只需要寻找 log2(n) 次,即时间复杂度为 O(logn)。

而区间查询的时间复杂度也是 O(logn),我们可以考虑两种情况:

  • 查询范围只包含一个子区间,那么也是最多 log2(n) 次查找
  • 查询返回包含两个子区间,那么对于再下一层的区间,只有两种情况,一是又包含两个子区间,但由于上一层包含了右子区间,那么当前层的右区间是可以直接返回的,继续向左区间下层搜索即可。二是只包含一个子区间,那么只需从包含的那个子区间向下继续搜索即可,时间复杂度仍是 O(logn)。

    上面的论证并不详细,具体看代码部分。

定义线段树的结构体

const int N = 4e5+5; //总节点数
struct node
{
    int l/*区间左边界*/,r/*区间右边界*/,sum/*区间元素之和*/;
}tree[N];

更新节点k的sum

void update(int k) // 更新节点k的sum;
{
    tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
    //一段区间的元素和等于它的子区间的元素和
}

因为我们每次修改一个元素的值,必定会对其上层的节点的 sum 产生影响,所以每次要重新计算一下节点 ksum

初始化线段树

void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	if(l==r)//递归到叶节点
	{
		tree[k].sum=number[l];//其中number数组为给定的初值
		return;
	}
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}

[l,r]lr 相等时,表示递归了叶节点了,要返回了,当前节点的 sum=number[l]

单点修改

void change(int k/*当前节点的编号*/,int x/*要修改节点的编号*/,int y/*要把编号为x的数字修改成y*/) // 单点修改
{
    //如果当前区间只包含一个元素,那么该元素一定就是我们要修改的。
	//由于该区间的sum一定等于编号为x的数字,所以直接修改sum就可以了。
	if(tree[k].l==tree[k].r)
    {
        tree[k].sum=y;
        return;
    }
	int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
	if(x<=mid)
        change(k<<1,x,y);//递归进左儿子
	else
        change(k<<1|1,x,y);//递归进右儿子
	update(k);//最后更新点k的值
}

区间查询

int query(int k/*当前节点的编号*/,int l/*当前查询区间的左边界*/,int r/*当前查询区间的右边界*/) // 区间查询
//当前到了编号为k的节点,查询[l,r]范围的和
{
    //如果当前区间是询问区间的子区间,可以直接返回
	if(tree[k].l>=l&&tree[k].r<=r)
        return tree[k].sum;
	int mid=(tree[k].l+tree[k].r)>>1; //计算下一层子区间的左右边界
    int res=0;
    //如果询问区间包含左子区间的部分
	if(l<=mid)
        res += query(k<<1,l,r);
    //如果询问区间包含右子区间的部分
	if(r>mid)
        res += query(k<<1|1,l,r);
	return res;
}

区间修改区间查询

区间修改与单点修改不同的是其每次是对一个区间[li,ri]进行修改。如果按照单点修改的写法,那么每次都更新到叶节点,那么时间复杂度最大为 O(nlogn),即 n 节点都递归 logn 层。这样的时间复杂度是不可接受的。

这时可以引入一个叫懒标记(lazy)的东西,我们每次区间,向下更新时,如果要更新的区间包含当前节点的区间,就不向下更新了,而是维护一个 lazy,把要修改的值更新入 lazy 中。之后当需要进入当前节点下面节点时,再将 lazy 下传下去,去更新下面节点的区间和以及 lazy,再将当前节点清零。

其实可以发现这和区间查询的方式很类似,所以其时间复杂度也为 O(logn)。

定义线段树的结构

const int N = 4e5+5; //总节点数
struct node
{
    int l/*区间左边界*/,r/*区间右边界*/,sum/*区间元素之和*/,lazy/*懒惰标记*/;
}tree[N];

相比于单点修改多了 lazy 懒标记。

懒标记下传

void pushdown(int k) //懒标记下传
{
    tree[k<<1].lazy += tree[k].lazy;
    tree[k<<1|1].lazy += tree[k].lazy;
    tree[k<<1].sum += tree[k].lazy * (tree[k<<1].r - tree[k<<1].l + 1); //左节点要加上懒标记的值*(左节点区间长)
    tree[k<<1|1].sum += tree[k].lazy * (tree[k<<1|1].r - tree[k<<1|1].l + 1); //右节点要加上懒标记的值*(右节点区间长)
    tree[k].lazy = 0; //下传到子节点之和当前点的懒标记要清零
}

先更新子节点的懒标记,再更新子节点的元素和,最后对懒标记清零。

更新节点k的sum

void update(int k) // 更新节点k的sum;
{
    tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
    //一段区间的元素和等于它的子区间的元素和
}

初始化线段树

void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	lazy[k]=0; // 初始懒标记
	if(l==r)//递归到叶节点
	{
		tree[k].sum=number[l];//其中number数组为给定的初值
		return;
	}
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}

区间修改

void add(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, int val/*区间每个元素增加的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].sum += (tree[k].r - tree[k].l + 1)*val;
        tree[k].lazy += val;
        return ;
    }
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
    if(tree[k].lazy)
        pushdown(k);
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        add(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        add(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}

区间查询

int query(int k/*当前节点的编号*/,int l/*当前查询区间的左边界*/,int r/*当前查询区间的右边界*/) // 区间查询
//当前到了编号为k的节点,查询[l,r]范围的和
{
    //如果当前区间是询问区间的子区间,可以直接返回
	if(tree[k].l>=l&&tree[k].r<=r)
        return tree[k].sum;
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
	if(tree[k].lazy)
        pushdown(k);
	int mid=(tree[k].l+tree[k].r)>>1; //计算下一层子区间的左右边界
    int res=0;
    //如果询问区间包含左子区间的部分
	if(l<=mid)
        res += query(k<<1,l,r);
    //如果询问区间包含右子区间的部分
	if(r>mid)
        res += query(k<<1|1,l,r);
	return res;
}

例题与解析

【模板】线段树 1

题目链接

点我(^_^)

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含 3 3 3 4 4 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k
  2. 2 x y:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
样例输出 #1
11
8
20

提示

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n103 m ≤ 10 4 m \le {10}^4 m104
对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105

保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} 1018

【样例解释】

解题思路

这题就是区间修改与区间查询的模板题,需要注意的是,其元素之和的上限是 1e18,所以需要开 long long

AC代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+5; //总节点数
struct node
{
//    int l/*区间左边界*/,r/*区间右边界*/,sum/*区间元素之和*/,lazy/*懒惰标记*/;
    int l/*区间左边界*/,r/*区间右边界*/;
    long long sum;
    long long lazy;
}tree[N];
long long number[N];
void pushdown(int k) //懒标记下传
{
    tree[k<<1].lazy += tree[k].lazy;
    tree[k<<1|1].lazy += tree[k].lazy;
    tree[k<<1].sum += tree[k].lazy * (tree[k<<1].r - tree[k<<1].l + 1); //左节点要加上懒标记的值*(左节点区间长)
    tree[k<<1|1].sum += tree[k].lazy * (tree[k<<1|1].r - tree[k<<1|1].l + 1); //右节点要加上懒标记的值*(右节点区间长)
    tree[k].lazy = 0; //下传到子节点之和当前点的懒标记要清零
}
void update(int k) // 更新节点k的sum;
{
    tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
    //一段区间的元素和等于它的子区间的元素和
}
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	if(l==r)//递归到叶节点
	{
		tree[k].sum=number[l];//其中number数组为给定的初值
		return;
	}
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}
void add(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, long long val/*区间每个元素增加的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].sum += (tree[k].r - tree[k].l + 1)*val;
        tree[k].lazy += val;
        return ;
    }
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
    if(tree[k].lazy)
        pushdown(k);
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        add(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        add(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}
long long query(int k/*当前节点的编号*/,int l/*当前查询区间的左边界*/,int r/*当前查询区间的右边界*/) // 区间查询
//当前到了编号为k的节点,查询[l,r]范围的和
{
    //如果当前区间是询问区间的子区间,可以直接返回
	if(tree[k].l>=l&&tree[k].r<=r)
        return tree[k].sum;
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
	if(tree[k].lazy)
        pushdown(k);
	int mid=(tree[k].l+tree[k].r)>>1; //计算下一层子区间的左右边界
    long long res=0;
    //如果询问区间包含左子区间的部分
	if(l<=mid)
        res += query(k<<1,l,r);
    //如果询问区间包含右子区间的部分
	if(r>mid)
        res += query(k<<1|1,l,r);
	return res;
}

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1; i<=n; ++i)
        cin>>number[i];
    build(1,1,n);
    while(m--)
    {
        int opt;
        cin>>opt;
        if(opt == 1)
        {
            int x,y;
            long long z;
            cin>>x>>y>>z;
            add(1,x,y,z);
        }
        else
        {
            int x,y;
            cin>>x>>y;
            cout<<query(1,x,y)<<endl;
        }
    }
    return 0;
}

【模板】线段树 2

题目链接

点我(^_^)

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x x x

  • 将某区间每一个数加上 x x x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n , m , p n,m,p n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含若干个整数,表示一个操作,具体如下:

操作 1 1 1: 格式:1 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数乘上 k k k

操作 2 2 2: 格式:2 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数加上 k k k

操作 3 3 3: 格式:3 x y 含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和对 p p p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 3 3 的结果。

样例 #1

样例输入 #1
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
样例输出 #1
17
2

提示

【数据范围】

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 1 0 3 n \le 10^3 n103 m ≤ 1 0 4 m \le 10^4 m104
对于 100 % 100\% 100% 的数据: n ≤ 1 0 5 n \le 10^5 n105 m ≤ 1 0 5 m \le 10^5 m105

除样例外, p = 571373 p = 571373 p=571373

(数据已经过加强_

【样例说明】

故输出应为 17 17 17 2 2 2 40   m o d   38 = 2 40 \bmod 38 = 2 40mod38=2

解题思路

主要探究懒标记的更新及下传,这里假定当前区间的和为 sum,区间范围为 [l,r],下传下来的懒标记(即上一层的懒标记)为加法懒标记lazyk1,减法懒标记lazyk2,当前层的加法加法懒标记为 lazykk1 和 乘法懒标记 lazykk2, 因为子区间的两个区间更新方法相同,所以这里就只考虑一种。

那么对于加法,还是和上一题一样。
更新加法

sum = sum + (r-l+1)*val;
lazykk1 = lazykk1+val;

更新乘法
因为是每个数乘上一个数val,所以每个数乘val之和相当于每个数之和乘val
lazykk1 要乘上 val,这是因为 lazykk1*(r-l+1)*val = (lazykk1*val)*(r-l+1)

sum = sum*val
lazykk1 = lazykk1*val;
lazykk2 = lazykk2*val;

懒标记的下传

lazykk1 = lazykk1*lazyk2+lazykk1;
lazykk2 = lazykk2*lazyk2
sum = sum*lazyk2+lazyk1*(r-l+1);
lazyk1 = 0;
lazyk2 = 1; 

因为乘法对加法的影响已经在懒标记计算时进行过了,所以这里只需要乘上 sum 就可以了。注意 lazyk2 要初始化为 1,已经注意取模计算。

AC代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+10; //总节点数
long long mod;
struct node
{
    int l/*区间左边界*/,r/*区间右边界*/;
    long long lazy1/*加法懒惰标记*/,lazy2/*乘法懒惰标记*/,sum/*区间元素之和*/;

}tree[N];
long long number[N];
void pushdown(int k) //懒标记下传
{
    tree[k<<1].lazy1 = (tree[k<<1].lazy1*tree[k].lazy2+tree[k].lazy1)%mod;
    tree[k<<1|1].lazy1 = (tree[k<<1|1].lazy1*tree[k].lazy2+tree[k].lazy1)%mod;
    tree[k<<1].lazy2 = tree[k<<1].lazy2*tree[k].lazy2%mod;
    tree[k<<1|1].lazy2 = tree[k<<1|1].lazy2*tree[k].lazy2%mod;
    tree[k<<1].sum = tree[k<<1].sum*tree[k].lazy2%mod; //先乘上lazy2
    tree[k<<1|1].sum = tree[k<<1|1].sum*tree[k].lazy2%mod;
    tree[k<<1].sum = (tree[k<<1].sum + tree[k].lazy1*(tree[k<<1].r-tree[k<<1].l+1))%mod; //再加上区间长度*lazy1
    tree[k<<1|1].sum = (tree[k<<1|1].sum + tree[k].lazy1*(tree[k<<1|1].r-tree[k<<1|1].l+1))%mod;
    tree[k].lazy1 = 0; //下传到子节点之和当前点的懒标记要清零
    tree[k].lazy2 = 1;
}
void update(int k) // 更新节点k的sum;
{
    tree[k].sum=(tree[k<<1].sum+tree[k<<1|1].sum)%mod;
    //一段区间的元素和等于它的子区间的元素和
}
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	tree[k].lazy2 = 1;
	tree[k].lazy1 = 0;
	if(l==r)//递归到叶节点
	{
		tree[k].sum=number[l]%mod;//其中number数组为给定的初值
		return;
	}
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}

void add(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, long long val/*区间每个元素增加的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].sum = (tree[k].sum+(tree[k].r - tree[k].l + 1)*val)%mod;
        tree[k].lazy1 = (tree[k].lazy1+val)%mod;
        return ;
    }
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
    if(tree[k].lazy1 || tree[k].lazy2!=1)
        pushdown(k);
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        add(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        add(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}

void mul(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, long long val/*区间每个元素要乘的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].sum = tree[k].sum*val%mod;
        tree[k].lazy1 = tree[k].lazy1*val%mod;
        tree[k].lazy2 = tree[k].lazy2*val%mod;
        return ;
    }
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
    if(tree[k].lazy1 || tree[k].lazy2!=1)
        pushdown(k);
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        mul(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        mul(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}

long long query(int k/*当前节点的编号*/,int l/*当前查询区间的左边界*/,int r/*当前查询区间的右边界*/) // 区间查询
//当前到了编号为k的节点,查询[l,r]范围的和
{
    //如果当前区间是询问区间的子区间,可以直接返回
//    if(tree[k].l>=l&&tree[k].r<=r)
//     {
//         cout<<tree[k].l<<" "<<tree[k].r<<" "<<tree[k].sum<<endl;
//     }
	if(tree[k].l>=l&&tree[k].r<=r)
        return tree[k].sum%mod;
    //如果当前节点被打上了懒惰标记,那么就把这个标记下传
	if(tree[k].lazy1 || tree[k].lazy2!=1)
        pushdown(k);
	int mid=(tree[k].l+tree[k].r)>>1; //计算下一层子区间的左右边界
    long long res=0;
    //如果询问区间包含左子区间的部分
	if(l<=mid)
        res = (res + query(k<<1,l,r))%mod;
    //如果询问区间包含右子区间的部分
	if(r>mid)
        res = (res + query(k<<1|1,l,r))%mod;
	return res%mod;
}

int main()
{
    int n,m;
    cin>>n>>m>>mod;
    for(int i=1; i<=n; ++i)
        cin>>number[i];
    build(1,1,n);
    while(m--)
    {
        int opt;
        cin>>opt;
        if(opt == 1)
        {
            int x,y;
            long long z;
            cin>>x>>y>>z;
            mul(1,x,y,z);
        }
        else if(opt == 2)
        {
            int x,y;
            long long z;
            cin>>x>>y>>z;
            add(1,x,y,z);
        }
        else
        {
            int x,y;
            cin>>x>>y;
            cout<<query(1,x,y)<<endl;
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值