数据结构——点分治

一.基本思想

       点分治即是在树上使用分治算法以更高效的求解 树上路径 类问题。基本的时间复杂度为 O ( n l o g n ) O(nlog_n) O(nlogn)。对于一棵已经 确定了根 的树, 那么所有的路径即分为两类: 1.经过根的路径 2.不经过根的路径。 对于这些 不经过根 的路径, 显然 是在当前树的子树中, 我们需要做的只是 求出第一类路径 后 将根节点删除,在递归子树求解第二类根

二.基本步骤

1.求重心最为当前树的根

        这是点分治的复杂度降低关键, 如果我们每次选定当前树的的重心作为根, 那么与重心相连的最大子树的大小一定 不会超过该子树大小的一半 (可以用 反证法 去思考)。那么最多递归 l o g n log_n logn, 对于每一层而言的复杂度是 O ( n ) O(n) O(n), 则总复杂度为 O ( n l o g n ) O(nlog_n) O(nlogn)

下面是求重心的代码:

void get_root(int x, int fa){
	cnt[x] = 1, Maxn[x] = 0;//cnt[x] 表示以x为根的子树的大小, Maxn[x]表示与x相连的最大连通块大小
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		get_root(v, x);
		cnt[x] += cnt[v];
		Maxn[x] = max(Maxn[x], cnt[v]);
	}
	Maxn[x] = max(Maxn[x], all - cnt[x]);
	if(Maxn[x] < Maxn[root]) root = x;
}

2.求以重心为总根(当前求解的连通块的总根), 各个子树的大小

         这一步是为了后期求all 的值。

代码:

void get_size(int x, int fa){
	cnt[x] = 1;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		get_size(v, x);
		cnt[x] += cnt[v];
	}
}

3.求解经过当前root的路径

         针对不同的题目会有 不同的处理方法, 但有一个共同的框架: 一条 经过根的路径 是两条在根的不同子树的点到根的路径 拼接得到

<1>.依次递归每一个子树,并利用 前面子树的点到根的路径信息当前子树中的点到根的路径信息 更新答案。
<2>.加入当前子树中的点到根的所有路径信息。

代码板子:

void solve(int rt){//处理根为rt,经过rt的合法最优解
    vis[rt] = 1; 
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		....//给v节点附上一个初始信息
		calc(v, 0);//递归解决子树v
		change(v, n + 1, 1);//加上子树v中的信息 
	}
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		change(v, n + 1, 0);//回溯清空
	}
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		root = 0;
		all = cnt[v];
		get_root(v, n + 1);//找v所在联通块的中心
		get_size(root, n + 1);
		solve(root);//分治解决
	} 
}

三.例题

1.poj1741—tree

题目大意:给定一棵 n n n 个节点的树,每条边有边权,求出树上两点距离小于等于 k k k 的点对数量。


分析:考虑点分治, 开数组存下当前树中所有节点到根的路径长度, 排序后 双指针 扫描统计答案。 但是这样有可能会统计到在 同一棵子树的两个节点 之间的不合法路径, 需要考虑 容斥 减掉这部分不合法方案。 具体做法可以 给dis[v]赋上一个初值为 根与v连边的边权值,做一遍统计后减去所有 "合法"方案。(这里的 合法与上文提到的不合法路径是 一摸一样 的)。至此,问题得到解决。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 4e4 + 10;
int n, u, v, w, k, head[N], tot, T, cnt[N], f[N], now, INF = 1e9, d[N], dis[N], dcnt, root, ans;//cnt[i] 表示以i为根的子树的大小  f[i] 表示与i相连的联通块的最大大小 
bool vis[N];//标记一个点是否被删除 
struct edge{
	int v, last, w;
}E[N * 2];

void add(int u, int v, int w){
	E[++tot].v = v;
	E[tot].w = w;
	E[tot].last = head[u];
	head[u] = tot;
}

void get_size(int x, int fa){
	cnt[x] = 1;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		get_size(v, x);
		cnt[x] += cnt[v];
	}
}

void get_root(int x, int fa){//找根 
	cnt[x] = 1, f[x] = 0 ;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;//父亲和已经删过的的点都不进入
		get_root(v, x);
		cnt[x] += cnt[v];
		f[x] = max(f[x], cnt[v]); 
	}
	f[x] = max(f[x], now - cnt[x]);
	if( f[x] < f[root] ) root = x;
}

void get_dis(int x, int fa){
	if(d[x] > k) return ;
	dis[++dcnt] = d[x];
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		d[v] = d[x] + E[i].w;
		get_dis(v, x);
	}
}

int count(int x, int w){
	d[x] = w, dcnt = 0;
	get_dis(x, 0);
	sort(dis + 1, dis + dcnt + 1);
	int l = 1, r = dcnt, sum = 0;
	while(l < r){//双指针
		if(dis[l] + dis[r] <= k) sum += r - l, l++;//r - l 对都可以
		else r--;
	}
	return sum;
}

void solve(int rt){//处理当前的根 
	ans += count(rt, 0);//计算贡献 
	vis[rt] = 1;
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		ans -= count(v, E[i].w);//减去不合法方案
		now = cnt[v], root = 0;
		get_root(v, 0);//求解v所在的联通块
		get_size(root, 0);
		solve(root);
	}
}

int main(){
	while(scanf("%d%d", &n, &k), n, k){
		memset(head, 0, sizeof(head));
		memset(vis, 0, sizeof(vis));
		tot = 0, root = ans = 0;
		f[0] = INF;
		now = n;
		for(int i = 1; i < n; i++){
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w);
			add(v, u, w);
		}	
		
		get_root(1, 0);//找到一个根 
		get_size(root, 0);//确定每个子树的大小 
		solve(root);	
		printf("%d\n", ans);
	}
	return 0;
}

2.[IOI2011]Race

题目大意:给一棵树,每条边有权。求一条简单路径,权值和等于 k k k ,且边的数量最小。


分析:注意到 k k k 的范围较小,考虑开桶。 设 M i n [ x ] Min[x] Min[x] 表示 到当前子树为止, 到根的边权为 x x x 的路径的最小边数量。 那么对于当前的路径,设它到根的边权和为 s u m sum sum,边数为 n u m num num, 则可以用 n u m + M i n [ k − s u m ] num + Min[k - sum] num+Min[ksum] 更新 a n s ans ans。考虑完一棵子树后将它的信息加入即可。最后 初始化 M i n [ ] Min[] Min[],递归子连通块。 问题得到了解决。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
const int M = 1e6 + 10;
int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
	return x * f;
}

int n, k, u, v, w, tot, head[N], Min[M], cnt[N], f[N], INF = 1e9, all, root, ans, d[N], dep[N];//Min[i] 表示到当前根的距离为i的最短边数
bool vis[N], flag; 
struct edge{
	int v, last, w;
}E[N * 2];

void add(int u, int v, int w){
	E[++tot].v = v;
	E[tot].w = w;
	E[tot].last = head[u];
	head[u] = tot;
}

void get_root(int x, int fa){
	cnt[x] = 1, f[x] = 0;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		get_root(v, x);
		cnt[x] += cnt[v];
		f[x] = max(f[x], cnt[v]);
	}
	f[x] = max(f[x], all - cnt[x]);
	if(f[x] < f[root]) root = x;
}

void get_size(int x, int fa){
	cnt[x] = 1;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		get_size(v, x);
		cnt[x] += cnt[v];
	}
}

void get_dis(int x, int fa){
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		dep[v] = dep[x] + 1;
		d[v] = d[x] + E[i].w;
		get_dis(v, x);
	}
}

void calc(int x, int fa){
	if(d[x] > k) return ;
	ans = min(ans, dep[x] + Min[k - d[x]]);//更新答案
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		calc(v, x);
	}
}

void change(int x, int fa, int tp){//tp -> 0 赋初值, tp -> 1 加入信息
	if(d[x] > k) return ;
	if(tp) Min[d[x]] = min(Min[d[x]], dep[x]);
	else Min[d[x]] = n;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		change(v, x, tp);
	}
}

void solve(int rt){//处理根为rt,经过rt的合法最优解
    Min[0] = 0, vis[rt] = 1; 
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		d[v] = E[i].w, dep[v] = 1;//附上初值 
		get_dis(v, n + 1);
		calc(v, n + 1);
		change(v, n + 1, 1);//加上 
	}
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		change(v, n + 1, 0);//附回初值 
	}
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		root = n + 1;
		all = cnt[v];
		get_root(v, n + 1);//找v所在联通块的中心
		get_size(root, n + 1);
		solve(root);
	} 
}

int main(){
	
	n = read(), k = read();
	ans = n;
	for(int i = 1; i < n; i++){
		u = read(), v = read(), w = read();
		add(u, v, w);
		add(v, u, w);
	}
	
	for(int i = 0; i <= k; i++) Min[i] = n; 
	all = n, f[n + 1] = INF, root = n + 1;//有0号节点,所以初始root赋值为n + 1
	get_root(1, n + 1);
	get_size(root, n + 1);
	solve(root);
	
	ans = (ans == n ? -1 : ans);
	printf("%d\n", ans);
	
	return 0;
}

3.usaco yinyang 【bzoj3697】采药人的路径

题目大意:给出一棵n个点的树,每条边为黑色或白色。问满足以下条件的路径条数:路径上存在一个不是端点的点,使得两端点到该点的路径上两种颜色的边数都相等。


分析:
由于点分治 所有的限制条件是基于两端节点到根的路径信息之间的关系, 对于此题而言,若一条经过根的路径是平衡的, 首先应满足两条拼接的路径中的黑白数量是和相等,其次要满足这条路径之中有一个休息站。因此我们不能仅仅考虑节点到根路径的边权和, 还要考虑是否存在休息站。 仅开一维是不够的,我们考虑开两维存储信息。 设 g [ x ] [ 0 / 1 ] g[x][0/1] g[x][0/1] 表示当前为止,已经处理过的子树中到根节点边权和为 x x x, 这个点到根节点之间 无/有 休息站的 点的数量。 f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1] 表示当前子树中到根节点边权和为 x x x,这个点到根节点之间 无/有休息站的 点的数量。 那么设当前节点 到根节点 的边权和为 m m m。并且可以开一个桶 t [ x ] t[x] t[x] 标记 m m m 是否出现过。 若 t [ m ] > 0 t[m] > 0 t[m]>0, 则当前节点到根至少可以找到一个 休息点, 可以令 f [ m ] [ 1 ] + + f[m][1]++ f[m][1]++, 否则令 f [ m ] [ 0 ] + + f[m][0]++ f[m][0]++, 统计完一棵子树后, 它对答案的贡献即为 ∑ i = m m i n m m a x f [ i ] [ 1 ] ∗ g [ − i ] [ 1 ] + f [ i ] [ 1 ] ∗ g [ − i ] [ 0 ] + f [ i ] [ 0 ] ∗ g [ i ] [ 1 ] \sum_{i = m_{min}}^{m_{max}}f[i][1] * g[-i][1] + f[i][1]*g[-i][0]+f[i][0]*g[i][1] i=mminmmaxf[i][1]g[i][1]+f[i][1]g[i][0]+f[i][0]g[i][1],统计完后将 f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1] 加入 g g g 数组中。

代码:

#include<bits/stdc++.h>//路径有特殊限制, 可尝试多开一维 
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
	return x * f;
}

int INF = 1e9, t[N * 2];
int n, dis[N], u, v, w, head[N], tot, root, cnt[N], Maxn[N], all, max_deep, mx;
LL f[N * 2][2], g[N * 2][2], ans;//f[i][0/1]->当前子树到root距离为i并且无/有休息站, g[i][0/1]->到当前位置到root距离为i无/有休息站 
bool vis[N];
struct edge{
	int v, last, w;
}E[N * 2];

void add(int u, int v, int w){
	E[++tot].v = v;
	E[tot].w = w;
	E[tot].last = head[u];
	head[u] = tot;
}

void get_root(int x, int fa){//找x所在连通块的中心 
	cnt[x] = 1, Maxn[x] = 0;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		get_root(v, x);
		cnt[x] += cnt[v];
		Maxn[x] = max(Maxn[x], cnt[v]);
	}
	Maxn[x] = max(Maxn[x], all - cnt[x]);
	if(Maxn[x] < Maxn[root]) root = x;
}

void get_size(int x, int fa){
	cnt[x] = 1;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		get_size(v, x);
		cnt[x] += cnt[v];
	}
}

void get(int x, int fa){//t[]不可标记
	max_deep = max(max_deep, abs(n - dis[x]));//求当前子树中的最大上下差值
	if(t[dis[x]]) f[dis[x]][1]++;
	else f[dis[x]][0]++;
	t[dis[x]]++;//放下面!!!!!!!!!!!! 
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(v == fa || vis[v]) continue;
		dis[v] = dis[x] + E[i].w;
		get(v, x);
	}
	t[dis[x]]--;//回溯时清空t数组\
}

void change(int max_deep){
	for(int i = n - max_deep; i <= n + max_deep; i++){//将f的信息加入g并清空
		g[i][0] += f[i][0];
		g[i][1] += f[i][1];
		f[i][0] = f[i][1] = 0;
	}
}

void clear(int mx){//清空g数组
	for(int i = n - mx; i <= n + mx; i++) g[i][0] = g[i][1] = 0, f[i][0] = f[i][1] = 0;
}

void solve(int rt){
	vis[rt] = 1; g[n][0] = 1;//根自己本身的贡献 
	mx = 0;//mx表示处理当前根时的最大上下差值 
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		dis[v] = n + E[i].w, max_deep = 1;//max_depp表示处理v时所涉及的最大上下差值  向右偏移n防止越界
		get(v, 0);
		mx = max(mx, max_deep);
		ans = ans + (g[n][0] - (1LL * 1)) * f[n][0];//到根节点是正好平衡*另一边到根节点正好平衡  (-1) 是减去自己一个根的贡献
		for(int j = n - max_deep; j <= n + max_deep; j++){//求答案
			ans = ans + f[j][1] * g[2 * n - j][1] + f[j][1] * g[2 * n - j][0] + f[j][0] * g[2 * n - j][1];
		}
		change(max_deep);
	}
	clear(mx);
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		root = 0;
		all = cnt[v];
		get_root(v, 0);
		get_size(root, 0);
		solve(root);
	}
}

int main(){
	
	n = read();
	for(int i = 1; i < n; i++){
		u = read();
		v = read();
		w = read();
		if(w == 0) w = -1;//将0赋值为-1, 技巧
		add(u, v, w);
		add(v, u, w);
	}
	
	Maxn[0] = INF, root = 0, all = n;
	get_root(1, 0);
	get_size(root, 0);
	solve(root);
	
	printf("%lld\n", ans);
	
	return 0;
}

4.[国家集训队]聪聪可可


分析:求出所有边权和为三的倍数的路径( x x x -> y y y)的个数,仅需满足 (( ( d i s [ x (dis[x (dis[x -> r o o t ] ) root]) root])% 3 3 3 + + + ( d i s [ y (dis[y (dis[y -> r o o t ] ) root]) root])% 3 3 3)) % 3 3 3 = 0。类比 Race 开桶统计即可。

5.树分治例2[SPOJ1825]免费旅行II

题目大意:求一条树上路径的边权和最大值,满足这条路径经过的拥挤点不超过 K K K 个。


分析:与前面的题类似,设对于根到当前节点之间的路径已经经过了 m m m 个拥挤点,则需要求出 已经处理的经过的拥挤点不超过 k − m k - m km 的路径的最大路径和。开一个 树状数组 维护即可。

树状数组代码:

int lowbit(int x){
	return x & -x;
}

LL ask(int x){//查询前缀最大
	LL res = NNF;
	for(; x; x -= lowbit(x)) res = max(res, c[x]);
	res = max(res, c[0]);
	return res; 
}

void add(int x, LL y){//修改
	if(x != 0) for(; x <= k; x += lowbit(x)) c[x] = max(c[x], y);
	else c[0] = max(c[0], y);
}

清空:

void modify(int x){
	if(x != 0) for(; x <= k; x += lowbit(x)) c[x] = NNF;
	else c[0] = NNF;
}

6.路径规划

题目大意:给定一棵n个点的无根树,树上的边有一个权值,要求找出一条路径使得该路径权的最小值乘边权和最大


分析:由于 最小值可能在任意一条路径(拼接的两条)中产生,因此我们考虑 固定最小值。及对于当前处理的路径, 以它中的边权最小值作为拼接路径的最小值处理。那么我们只需查询 已经处理过的到根的路径中最小值大于当前最小值的路径和最大值。用 线段树 查询即可。 但线段树常数较大,注意到我们查询的是一个 后缀, 可以用 树状数组 实现。只需要将 原来查询和修改的枚举顺序调换即可

代码:

#include<bits/stdc++.h>//查询和插入相反则为后缀 
using namespace std;

typedef long long LL;
const int N = 3e5 + 10;
const int M = 1e6 + 10;

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
	return x * f;
}

struct edge{
	int v, last;
	int w;
}E[N * 2];

stack< int > s;
int n, u, v, w, head[N], tot, cnt[N], Maxn[N], root, all, f[N], p, l[N];
int INF = 1e8;
int dis[N], Minn, w_max;
LL ans, sum, c[M];
bool vis[N];

void add(int u, int v, int w){
	E[++tot].v = v;
	E[tot].w = w;
	E[tot].last = head[u];
	head[u] = tot;
}

void get_root(int x, int fa){
	cnt[x] = 1, Maxn[x] = 0;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		get_root(v, x);
		cnt[x] += cnt[v];
		Maxn[x] = max(Maxn[x], cnt[v]);
	}
	Maxn[x] = max(Maxn[x], all - cnt[x]);
	if(Maxn[x] < Maxn[root]) root = x;
}

void get_size(int x, int fa){
	cnt[x] = 1;
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		get_size(v, x);
		cnt[x] += cnt[v];
	}
}

int lowbit(int x){return x & -x;}

LL ask(int x){//查询
   LL res = 0;
   for(; x < M; x += lowbit(x)) res = max(res, c[x]);//查询后缀,每次加 lowbit(x)
   return res;
}

void add(int x, LL y){//修改,每次减lowbit(x)
	for(; x; x -= lowbit(x)) c[x] = max(c[x], y);
}

void del(int x){//赋初值
	for(; x; x -= lowbit(x)) c[x] = 0;
}

void op(int x, int fa){//处理x这棵子树 
	LL tmax = ask(Minn);//查询
	ans = max(ans, (1LL * (tmax + sum)) * 1LL * Minn);
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		sum = sum + 1LL * E[i].w;
		int t_min = Minn;
		Minn = min(Minn, E[i].w);
		op(v, x);
		sum = sum - 1LL * E[i].w;
		Minn = t_min;
	} 
}

void get(int x, int fa){//将x这棵子树贡献加上 
	add(Minn, sum);
	s.push(Minn);
	for(int i = head[x]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v] || v == fa) continue;
		sum = sum + 1LL * E[i].w;
		LL t_min = Minn;
		Minn = min(Minn, E[i].w);
		get(v, x);
		sum = sum - 1LL * E[i].w;
		Minn = t_min;
	}
}

void solve(int rt){
	vis[rt] = 1, p = 0;
	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		f[++p] = v;
		l[p] = E[i].w;
		dis[v] = E[i].w, Minn = E[i].w, sum = 1LL * E[i].w;
		op(v, 0);
		sum = 1LL * E[i].w, Minn = E[i].w;
		get(v, 0);
	}
	
	while(!s.empty()){
		int Top = s.top(); 
		s.pop();
		del(Top);
	}
	 
	for(int i = p; i >= 1; --i){
		int v = f[i];
		if(vis[v]) continue;
		dis[v] = l[i], Minn = l[i], sum = 1LL * l[i];
		op(v, 0);
		sum = 1LL * l[i], Minn = l[i];
		get(v, 0);
	}
	
    while(!s.empty()){
    	int Top = s.top();
    	s.pop();
    	del(Top);
	}

	for(int i = head[rt]; i; i = E[i].last){
		int v = E[i].v;
		if(vis[v]) continue;
		root = 0, all = cnt[v];
		get_root(v, 0);
		get_size(root, 0);
		solve(root);
	}
}

int main(){

	n = read();
	for(int i = 1; i < n; ++i){
		u = read(), v = read(), w = read();
		add(u, v, w);
		add(v, u, w);
	}
	
	Maxn[0] = INF, root = 0, all = n;
	get_root(1, 0);
	get_size(root, 0);
	solve(root);
	
	printf("%lld\n", ans);
	
	return 0;
}
//152147457856000000
//299999000000000000
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值