11.6-11.13小结

1.一小段废话

        最近事情真的很多,各种期中考试,大作业什么什么的,再加上树链剖分非常好调试码量非常小,所以刷的题很少能刷都不错了好嘛。笔者刚刚赶完明天要交的论文,就来写小结了,以后要不就按周写小结吧,显得有条理一些。刚学了树链剖分,这周大部分的时间做树剖,水了不少模板题。水平有限,总结是写给自己看的,可能写的不太好。

2.树链剖分的基本概念与实现

        前置知识:图遍历(dfs),线段树

假如有一个数组,需要维护其一些性质,如:最大值,最小值,异或和,区间和,颜色数等,我们可以考虑用线段树维护。但我们需要维护树上的两个点(u,v)的路径上经过的点的性质,就需要用到今天所述的树链剖分了。

2.1树链剖分基础概念

重儿子:某结点的子结点中大小最大的结点。

轻儿子:某结点除了重儿子其他的结点,特别的是,根结点是轻儿子。

重链:链上所有元素(除了顶点)都是重儿子。

轻链:其他的链。

如果这些概念比较枯燥的话,可以通过一张图来直观感受。(假定一号结点为根节点,结点编号为1~n)

如图所示,加粗的结点就是一条重链

2.2具体算法实现

        树链剖分时,实现两次dfs。第一次dfs的目的:处理出结点的深度,重儿子,父亲结点,由于重儿子定义可知,我们需要处理出每个结点的子树的大小。第二次dfs:处理出每条链顶端的元素,按dfs的顺序处理出每个结点的新编号idx,然后进行后续线段树的操作。

2.3对变量的解释

dep[u]:记录u结点的深度。

fa[u]:记录u结点的父亲结点。

sz[u]:记录以u为根的子树大小。

son[u]:记录u的重儿子。

top[u]:记录u所在链的链头元素。

idx[u]:记录u结点的新编号。

nw[cnt]:新编号对应的权值

 3.题目

3.1洛谷 P3384模板题

操作:1.将x到y路径的结点全部加上k,我们将top[x]和top[y]深的那一个不断向上跳,通过线段树累加实现区间加。

2.基本与1一致,将区间加改为查找。

3.我们处理出的dfs序一定满足这样的规律:第i个元素是某颗子树的根结点,sz[i]是这颗子树的大小,从i~i+sz[i]-1也全是这颗子树上的元素,所以处理以x为结点的子树就是线段树上处理x~x+sz[x]-1,将该区间每个元素加上z,用线段树维护。

4.操作基本与3一致,将区间加改为查找。

注意事项:1.线段树区间加操作记得维护懒标记。

2.记得开long long。

直接贴代码吧。

#include<iostream>
#include<vector>
#include<cstring>
#define ll long long 
#define int long long 
using namespace std;
using namespace std;
const int N = 1e5 + 10;
ll  w[N], nw[N];
ll sumv[N << 2], tag[N << 2];
int Size[N], son[N], top[N], dep[N], fa[N],n, m, r, p,cnt, id[N];
vector<int>e[N];
void pushup(int id) {
	sumv[id] = sumv[id << 1] + sumv[id << 1 | 1];
	//sumv[id] %= p;
}
void build(int id, int l, int r) {
	if (l == r) {
		sumv[id] = nw[l];
		return;
	}
	int mid = l + r >> 1;
	build(id << 1, l, mid);
	build(id << 1|1, mid + 1, r);
	pushup(id);
}
void pushdown(int id, int l, int r) {
	if (tag[id] == 0)return;
	ll k = tag[id];
	tag[id << 1] += k;
	tag[id << 1 | 1] += k;
	int mid = l + r >> 1;
	sumv[id << 1] += (mid - l + 1) * k;
	sumv[id << 1 | 1] += (r - mid) * k;
	tag[id] = 0;//回收标记不能忘
}
void add(int id, int l, int r, int x, int y, ll k) {
	if (x <= l && y >= r) {
		sumv[id] += (r - l + 1) * k;
		//sumv[id] %= p;
		tag[id] += k;
		return;
	}
	pushdown(id, l, r);
	int mid = l + r >> 1;
	if (x <= mid)add(id << 1, l, mid, x, y, k);
	if (y > mid)add(id << 1 | 1, mid + 1, r, x, y, k);
	pushup(id);
}
void modify(int u, int v,ll k) {
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]])swap(u, v);
		add(1, 1, n, id[top[u]], id[u], k);
		u = fa[top[u]];
	}
	if (dep[u] < dep[v])swap(u, v);
	add(1, 1, n, id[v], id[u],k);
	return;
}
int  ask(int id, int l, int r, int x, int y) {
	if (x <= l && y >= r)return sumv[id];
	pushdown(id, l, r);
	int mid = l + r >> 1;
	ll ans = 0;
	if (x <= mid)ans += ask(id << 1, l, mid, x, y);
	if (y > mid)ans += ask(id << 1 | 1, mid + 1, r, x, y);
	//ans %= p;
	return ans;
}
int query(int u, int v) {
	ll res = 0;
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]])swap(u, v);
		res += ask(1, 1, n, id[top[u]], id[u]);//累加权值和
		res %= p;
		u = fa[top[u]];
	}
	if (dep[u] < dep[v])swap(u, v);
	res += ask(1, 1, n, id[v], id[u]);
	return res;
}
void dfs1(int u, int f) {
	//处理出深度dep,Size大小,fa父亲结点,重儿子son
	dep[u] = dep[f] + 1;
	Size[u] = 1;
	fa[u] = f;
	for (int v : e[u]) {
		if (v == f)continue;
		dfs1(v, u);
		Size[u] += Size[v];
		if (Size[son[u]] < Size[v])son[u] = v;
	}
}
void dfs2(int u, int t) {
	//处理每一条链的顶点top
	//结点被剖后的新编号id,新编号对应的新权值nw
	top[u] = t;
	id[u] = ++cnt;//存储剖分后的新编号
	nw[cnt] = w[u];//存储新编号在树中的权值 
	if (!son[u])return;
	dfs2(son[u], t);
	for (int v : e[u]) {
		if (v == fa[u] || son[u] == v)continue;//不往重儿子和父亲走
		dfs2(v, v);
	}
}
signed  main() {
	cin >> n >> m >> r >> p;
	for (int i = 1; i <= n; i++)cin >> w[i];
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(r, 0);
	dfs2(r, r);
	build(1, 1, n);
	/*for (int i = 1; i <= n; i++)cout << nw[i] << ' ';
	cout << endl;
	for (int i = 1; i <= n; i++)cout << ask(1, 1, n, i, i) << ' ';
	cout << endl;*/
	while (m--) {
		int op;
		cin >> op;
		if (op == 1) {
			int x, y;
			ll z;
			cin >> x >> y >> z;
			modify(x, y, z);
		}
		else if (op == 2) {
			int x, y;
			cin >> x >> y;
			cout << query(x, y)%p << endl;
		}
		else if (op == 3) {
			int root;
			ll k;
			cin >> root >> k;
			add(1, 1, n, id[root], id[root] + Size[root] - 1, k);
		}
		else if (op == 4) {
			int root;
			cin >> root;
			cout << ask(1, 1, n, id[root], id[root] + Size[root] - 1)%p<<endl;
		}
	}
	return 0;
}

3.2洛谷P2146软件包管理器

        写这道题的状态非常好啊,一发就过了。“如果软件包 a 依赖软件包 b,那么安装软件包 a 以前,必须先安装软件包 b。”同时,如果想要卸载软件包 b,则必须卸载软件包 a。”题意分析:我们只有两个操作,install安装和uninstall卸载,安装操作一定是自上而下的,卸载操作一定是自下而上的,不妨假定软件包已安装为1,未安装为0,维护区间和即可。至于安装和卸载操作:对于安装,就是查询从树根到当前结点的0的个数,可以用点深度减去线段树维护的区间和,然后再将这段区间全部修改为1。卸载就是把安装反过来。本题几乎没有坑点,考虑好如何维护标记即可。

部分代码如下。线段树部分略。

void uninstall(int x){//即查询x的子树的1的个数
	cout<<ask(1,1,n,id[x],id[x]+sz[x]-1)<<endl;
	modify(1,1,n,id[x],id[x]+sz[x]-1,0);//区间内修改为0 
}
void install(int u){
	//即查询从根节点1~x的这条链的0的个数
	//而线段树维护的是1的个数,我们只需要用总数(可以用dep[u]代替)-sumv即可
	int ans=0;
	int sum=dep[u];
	while(top[u]!=top[1]){
		//if(dep[top[x]]<dep[top[1]])swap(x,1);//感觉这句话没有必要
		ans+=ask(1,1,n,id[top[u]],id[u]);
		modify(1,1,n,id[top[u]],id[u],1);//全部改成1 
		u=fa[top[u]];
	}
	//if(dep[u]>dep[1])swap(x,1);//同上 感觉是废话
	ans+=ask(1,1,n,id[1],id[u]);
	modify(1,1,n,id[1],id[u],1);//全部下载
	cout<<sum-ans<<endl;
}

3.3P4315月下“毛景树”

        这道题的难点就是边权换点权了,之前我们处理的树链剖分问题都是基于点权解决的,可以注意到树的一个性质,一颗n结点的树,一定是有n-1条边的,那么我们可以不计根结点的点权,将边权下放,即让一条边的边权改为这条边深度更深的结点的点权,注意修改和查询操作中不计最终查询到的根结点即可。如下图所示,改成的有向边的形式即为点权的下放顺序。

此外,分析该题需要维护的操作:1.Change操作:将第k条树枝的权值改为w:由于用链式前向星存图,每次存储两条边,所以实际上是将e[k/2].w修改为w,如果不用链式强向星的话,就在第一次dfs的过程中记录下第i条边记录的结点是v也是可以的,我认为后者是更直白的。然后实际上就是将v这个点进行单点修改即可。

2.Cover操作:区间覆盖,将从u到v的路径上的果子全部改成w个。只要注意好不要计算LCA(u,v)即可,注意线段树部分需要打上cov标记(LCA表示最近公共祖先)。

3.Add操作:区间加,将u,v路径的果子全部加上w。不要操作LCA(u,v)。

4.查询操作:查询u,v的最大值,用线段树维护,不计LCA(u,v)。

注意事项:1.注意懒标记的顺序。覆盖标cov的优先级高于add标记,也就是说当存在cov标记时,要立刻将add清空。

2.覆盖操作修改的值可能为0,所以cov标记要初始化为-1,回收标记也修改成-1。

似乎这道题也没什么别的坑点,为什么我调了一个下午才出来……

已经尽量删调试过程了,没删干净我也没办法

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+10;
int fa[N],dep[N],top[N],idx[N],sz[N],son[N],nw[N],w[N],cnt,head[N],len,pos[N];
int maxv[N<<2],add[N<<2],cov[N<<2],n,m; 
struct edge{
	int v,w,i;
};
vector<edge>e[N];
void pushup(int id){
	maxv[id]=max(maxv[id<<1],maxv[id<<1|1]);
}
void build(int id,int l,int r){
	cov[id]=-1;
	if(l==r){
		maxv[id]=nw[l];
		return;
	}
	int mid=l+r>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	pushup(id);
}
void pushdown(int id,int l,int r){
	if(~cov[id]){
		maxv[id<<1]=maxv[id<<1|1]=cov[id];
		cov[id<<1]=cov[id<<1|1]=cov[id];
		add[id<<1]=add[id<<1|1]=0;
		cov[id]=-1;//回收标记 
	}
	add[id<<1]+=add[id];
	add[id<<1|1]+=add[id];
	maxv[id<<1]+=add[id];
	maxv[id<<1|1]+=add[id];
	add[id]=0;//回收标记 
}
void Cover(int id,int l,int r,int x,int y,int k){//区间覆盖 
	if(x<=l&&y>=r){
		cov[id]=k;
		maxv[id]=k;
		add[id]=0;
		return;
	}
	pushdown(id,l,r);
	int mid=l+r>>1;
	if(x<=mid)Cover(id<<1,l,mid,x,y,k);
	if(y>mid)Cover(id<<1|1,mid+1,r,x,y,k);
	pushup(id);
}
void Add(int id,int l,int r,int x,int y,int k){
	if(x<=l&&y>=r){
		//cov[id]=k;
		maxv[id]+=k;
		add[id]+=k;
		return;
	}
	pushdown(id,l,r);
	int mid=l+r>>1;
	if(x<=mid)Add(id<<1,l,mid,x,y,k);
	if(y>mid)Add(id<<1|1,mid+1,r,x,y,k);
	pushup(id);
}
int query(int id,int l,int r,int x,int y){
	if(x<=l&&y>=r)return maxv[id];
	int mid=l+r>>1,ans=0;
	pushdown(id,l,r);
	if(x<=mid)ans=max(ans,query(id<<1,l,mid,x,y));
	if(y>mid)ans=max(ans,query(id<<1|1,mid+1,r,x,y));
	return ans;
}
void dfs1(int u,int f){
	//第一遍dfs序,主要处理son,dep,top,sz
	sz[u]=1;
	son[u]=0;
	dep[u]=dep[f]+1;
	fa[u]=f;
	for(edge t:e[u]){
		int v=t.v;
		int i=t.i;
		if(v==f)continue;
		dfs1(v,u);
		pos[i]=v;
		w[v]=t.w;//记录权
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v])son[u]=v;
	}
}
void dfs2(int u,int t){
	top[u]=t;
	idx[u]=++cnt;
	nw[cnt]=w[u];
	if(!son[u])return;//说明走到了叶子节点
	dfs2(son[u],t); //先走重子链 
	for(edge t:e[u]){
		int v=t.v;
		if(v==son[u]||v==fa[u])continue;
		dfs2(v,v);//走轻子链 
	}
}
void TrCover(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		Cover(1,1,n,idx[top[u]],idx[u],k);
		u=fa[top[u]];
	}
	if(idx[u]<idx[v])swap(u,v);
	Cover(1,1,n,idx[v]+1,idx[u],k);
}
void TrAdd(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		Add(1,1,n,idx[top[u]],idx[u],k);
		u=fa[top[u]];
	}
	if(idx[u]<idx[v])swap(u,v);
	Add(1,1,n,idx[v]+1,idx[u],k);
}
int  Trquery(int u,int v){
	int ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		ans=max(ans,query(1,1,n,idx[top[u]],idx[u]));
		u=fa[top[u]];
	}
	if(idx[u]<idx[v])swap(u,v);
	ans=max(ans,query(1,1,n,idx[v]+1,idx[u]));
	return ans;
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w,i});
		e[v].push_back({u,w,i});
	}
	dfs1(1,0);

	dfs2(1,1);
	build(1,1,n);
	while(1){
		string s;
		cin>>s;
		if(s=="Stop")break;
		else if(s=="Add"){
			int u,v,k;
			cin>>u>>v>>k;
			TrAdd(u,v,k);
		}else if(s=="Cover"){
			int u,v,k;
			cin>>u>>v>>k;
			TrCover(u,v,k);
		}else if(s=="Change"){
			int x,k;
			cin>>x>>k;
			int v=pos[x];
			int u=fa[v];
			TrCover(u,v,k);
			//Change(1,1,n,pos[x],k);
		}else{
			int u,v;
			//cout<<1;
			cin>>u>>v;
			cout<<Trquery(u,v)<<endl;
		}
	}
	return 0;
}

3.4P1505旅游

同样的边权化点权,这里不再赘述了。

操作:1:C操作,修改单条边权,同上。

2.N操作:区间取相反数,这里可以思考以下如何放懒标记维护线段树。因为两次取相反数等于不变,可以用异或来维护标记。这道题需要查询最大值,最小值,和。每当进行区间取反操作时,

sum直接取反,max和min分别取反交换即可。

三个查询操作也同上。

注意事项:1.pushdown过程中一定是用异或维护懒标记。

2.在单点修改中,一定要pushdown。因为区间取反后对修改是会有影响的。

这题似乎比P4315容易一些,只是码量长(?

贴个代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
int dep[N],fa[N],sz[N],top[N],son[N],idx[N],w[N],nw[N],head[N],cnt,len,n,m;
struct edge{
	int v,w,next;
	edge(){}
	edge(int a,int b,int c){
		v=a,w=b,next=c;//寸图 
	}
}e[N<<1];
struct node{
	int u,v;
}c[N];
void add(int u,int v,int w){
	e[len]=edge(v,w,head[u]);
	head[u]=len++;
}
int maxv[N<<2],minv[N<<2],sumv[N<<2],tag[N<<2];//tag维护一个相反标
void pushup(int id){
	maxv[id]=max(maxv[id<<1],maxv[id<<1|1]);
	minv[id]=min(minv[id<<1],minv[id<<1|1]);
	sumv[id]=	 sumv[id<<1]+sumv[id<<1|1];
}
void pushdown(int id,int l,int r){
	if(!tag[id])return;
	tag[id<<1]^=1;
	tag[id<<1|1]^=1;//下传标记
	int _max=maxv[id<<1],_min=minv[id<<1];
	maxv[id<<1]=-_min;
	minv[id<<1]=-_max;
	sumv[id<<1]*=-1;
	_max=maxv[id<<1|1],_min=minv[id<<1|1 ];
	maxv[id<<1|1]=-_min;
	minv[id<<1|1]=-_max;
	sumv[id<<1|1]*=-1;
	tag[id]=0;
}
void build(int id,int l,int r){
	if(l==r){
		sumv[id]=nw[l];
		maxv[id]=nw[l];
		minv[id]=nw[l];
		return;
	}
	int mid=l+r>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	pushup(id);
}
void Change(int id,int l,int r,int x,int k){//将x点修改为k 单点修改 
	if(l==r){
		sumv[id]=k;
		minv[id]=k;
		maxv[id]=k;
		return;
	}
	int mid=l+r>>1;
	pushdown(id,l,r);
	if(x<=mid)Change(id<<1,l,mid,x,k);
	else Change(id<<1|1,mid+1,r,x,k);
	pushup(id);
}
void Opp(int id,int l,int r,int x,int y){//区间修改 
	if(x<=l&&y>=r){
		int max_=maxv[id],min_=minv[id];
		maxv[id]=-min_;
		minv[id]=-max_;
		sumv[id]*=-1;
		tag[id]^=1;
		return;
	}
	int mid=l+r>>1;
	pushdown(id,l,r);
	if(x<=mid)Opp(id<<1,l,mid,x,y);
	if(y>mid)Opp(id<<1|1,mid+1,r,x,y);
	pushup(id);
}
int Max(int id,int l,int r,int x,int y){
	int ans=-1e9,mid=l+r>>1;
	if(x<=l&&y>=r)
		return maxv[id];
	pushdown(id,l,r);
	if(x<=mid)ans=max(ans,Max(id<<1,l,mid,x,y));
	if(y>mid)ans=max(ans,Max(id<<1|1,mid+1,r,x,y));
	return ans;
}
int Min(int id,int l,int r,int x,int y){
	int ans=1e9,mid=l+r>>1;
	if(x<=l&&y>=r)
		return minv[id];
	pushdown(id,l,r);
	if(x<=mid)ans=min(ans,Min(id<<1,l,mid,x,y));
	if(y>mid)ans=min(ans,Min(id<<1|1,mid+1,r,x,y));
	return ans;
}
int Sum(int id,int l,int r,int x,int y){
	int ans=0,mid=l+r>>1;
	if(x<=l&&y>=r)
		return sumv[id];
	pushdown(id,l,r);
	if(x<=mid)ans+=Sum(id<<1,l,mid,x,y);
	if(y>mid)ans+=Sum(id<<1|1,mid+1,r,x,y);
	return ans;
}
void dfs(int u,int f){
	dep[u]=dep[f]+1;
	fa[u]=f;
	sz[u]=1;
	for(int i=head[u];~i;i=e[i].next){
		int v=e[i].v;
		if(v==f)continue;
		dfs(v,u);
		sz[u]+=sz[v];
		w[v]=e[i].w;//存边权 
		if(sz[son[u]]<sz[v])son[u]=v;//更新儿子 
	}
}
void dfs2(int u,int t){
	top[u]=t;
	idx[u]=++cnt;
	nw[cnt]=w[u];
	if(!son[u])return;
	dfs2(son[u],t);//访问重子链 
	for(int i=head[u];~i;i=e[i].next){
		int v=e[i].v;
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);//访问轻子链 
	}
}
void TrSum(int u,int v){
	int ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		ans+=Sum(1,1,n,idx[top[u]],idx[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	if(u!=v)ans+=Sum(1,1,n,idx[v]+1,idx[u]);
	cout<<ans<<endl;
}
void TrMax(int u,int v){
	int ans=-1e9;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		ans=max(ans,Max(1,1,n,idx[top[u]],idx[u]));
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	if(u!=v)ans=max(ans,Max(1,1,n,idx[v]+1,idx[u]));
	if(ans!=-1e9)cout<<ans<<endl;
}
void TrMin(int u,int v){
	int ans=1e9;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		ans=min(ans,Min(1,1,n,idx[top[u]],idx[u]));
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	if(u!=v)ans=min(ans,Min(1,1,n,idx[v]+1,idx[u]));
	if(ans!=1e9)cout<<ans<<endl;
}
void TrOpp(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		Opp(1,1,n,idx[top[u]],idx[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	if(u!=v)Opp(1,1,n,idx[v]+1,idx[u]);
}
int main(){
	memset(head,-1,sizeof head);
	cin>>n;
	for(int i=1;i<=n-1;i++){
		int u,v,w;
		cin>>u>>v>>w;
		u++,v++;
		add(u,v,w);
		add(v,u,w);
		c[i]={u,v};
	}
	dfs(1,0);
	dfs2(1,1);
	build(1,1,n);
	//for(int i=1;i<=n;i++)cout<<w[i]<<' ';
	cin>>m;
	for(int i=1;i<=m;i++){
		string s;
		cin>>s;
		if(s=="SUM"){
			int u,v;
			cin>>u>>v;u++,v++;
			TrSum(u,v);
		}else if(s=="MAX"){
			int u,v;
			cin>>u>>v;u++,v++;
			TrMax(u,v);
		}else if(s=="MIN"){
			int u,v;
			cin>>u>>v;u++,v++;
			TrMin(u,v);
		}else if(s=="N"){
			int u,v;
			cin>>u>>v;
			u++,v++;
			TrOpp(u,v);
		}else{
			int i,k;
			cin>>i>>k;
			int u=c[i].u;
			int v=c[i].v;
			if(dep[u]<dep[v])swap(v,u);
			Change(1,1,n,idx[u],k);
		}
	}
	return 0;
}

哎,感慨一下,还是分块好写啊。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值