【树链剖分模板】题
来源:互联网 发布:淘宝男士皮鞋哪家好 编辑:程序博客网 时间:2024/06/02 02:10
一、SPOJ375——Query on a tree (对边进行操作)
给一棵树 , N个点
两种操作:
1.i-ti :把第i条边的边权修改成ti
2.a-b :询问a到b路径上的边权最大值。
#include <bits/stdc++.h>using namespace std; /* */ const int MAXN = 20010; struct Edge { int to,next; }edge[MAXN*2]; int head[MAXN],tot; int top[MAXN];//top[v]表示v所在的重链的顶端节点 int fa[MAXN]; //父亲节点 int deep[MAXN];//深度 int num[MAXN];//num[v]表示以v为根的子树的节点数 int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置 int fp[MAXN];//和p数组相反 即 记录线段树中的某个位置的边的起点?int son[MAXN];//重儿子 int pos; // 每条边在线段树中的位置。int n; void init() { tot = 0; memset(head,-1,sizeof(head)); pos = 1;//边的序号其实是从1开始? memset(son,-1,sizeof(son)); } void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++; } void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son { deep[u] = d; fa[u] = pre; num[u] = 1; for(int i = head[u];i != -1; i = edge[i].next) { int v = edge[i].to; //因为路径是双向的,所以不能等于父节点 if(v == pre) continue; dfs1(v,u,d+1); //更新u节点的儿子数目 num[u] += num[v]; if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v son[u] = v; } } //因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]] void getpos(int u,int sp) //第二遍dfs求出top和p { top[u] = sp; //先找重儿子 if(son[u] != -1) // 存在重儿子 { //把边的位置标记一下 p[u] = pos++; //fp相当于是p的反函数,即记录线段树中某个位置的边 的在树中的起点。 fp[p[u]] = u; //更新重儿子 getpos(son[u],sp); } //如果到了叶子节点 else { //不再向下dfs p[u] = pos++; fp[p[u]] = u; return; } //更新其他的节点 for(int i = head[u] ; i != -1; i = edge[i].next) { int v = edge[i].to; if(v != son[u] && v != fa[u]) getpos(v,v); // 轻儿子的top[u]=u } } #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 int MAX[MAXN<<2]; int val[MAXN<<2]; void pushup(int rt){ MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]); } void build(int l,int r,int rt){ if(l==r){ MAX[rt]=val[l]; return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int p,int x,int l,int r,int rt){ // 单点更新 if(l==r){ MAX[rt]=x; return; } int m=(l+r)>>1; if(p<=m) update(p,x,lson); else update(p,x,rson); pushup(rt); } int query(int L,int R,int l,int r,int rt){ // 区间询问 if( L<=l &&r<=R){ return MAX[rt]; } int m=(l+r)>>1; int res=0; if(m>=L) res=max(res,query(L,R,lson)); if(R>m) res=max(res,query(L,R,rson)); return res; } int _find(int u,int v){ // 找 a-----b 中所有路径的最大值。 int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身 int temp=0; while(f1!=f2){ //从深度较深的开始查询 if(deep[f1]<deep[f2]){ swap(f1,f2); swap(u,v); } //查询一条重链上的最大值 temp=max(temp,query(p[f1],p[u],1,n,1)); u=fa[f1];f1=top[u]; } //如果f1=f2代表在同一条重链上m,如果u=v代表更新结束 if(u==v) return temp; if(deep[u]>deep[v]) swap(u,v); return max(temp,query(p[son[u]],p[v],1,n,1)); } int e[MAXN][3]; int main(){ int t; freopen("1.txt","r",stdin); scanf("%d",&t); while(t--){ init(); scanf("%d",&n); getchar(); for(int i=0;i<n-1;i++){ scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]); addedge(e[i][0],e[i][1]); addedge(e[i][1],e[i][0]); } dfs1(1,0,0); getpos(1,1); // 现在已经把树剖分完毕。 for(int i=0;i<n-1;i++){ if(deep[e[i][0]]<deep[e[i][1]]) // 调整每条路径的父亲,儿子关系。0>1 0为儿子1为父亲 swap(e[i][0],e[i][1]); val[ p[e[i][0]] ]=e[i][2]; // pi表示i和其父亲连边在线段树中的位置。 } build(1,n,1); // 以val建树 char op[10]; int u,v; while(scanf("%s",op)){ if(op[0]=='D') break; scanf("%d%d",&u,&v); if(op[0]=='Q') printf("%d\n",_find(u,v)); else update(p[e[u-1][0]],v,1,n,1); } } return 0; }
这是模板,可以发现,这个数据结构和大多数事物一样,揭开这一层神秘的面纱之后也就那么一回事。最关键的还是思维,这个很重要。
二、hdu3966(对点进行操作)
题意:给一棵树N个点,并给定各个点权的值,然后有3种操作:
I C1 C2 K: 把C1与C2的路径上的所有点权值加上K
D C1 C2 K:把C1与C2的路径上的所有点权值减去K
Q C:查询节点编号为C的权值
这个题只对点进行操作,那么我们线段树里面放点,
现在的问题是需要对某条路径上所有的点(所有的路径其实也同理)进行操作,那么该如何?
首先我们在线段树中是按重链在前,轻链在后 有顺序的放进去的(pos)
那么我们肯定也可以在线段树中区间更新(和边是同理)
例如要更新[l,r]
那么首先找到 top[l] top[r] 如果top相等 那么表示他们在一条重链上,此时其点(边)在线段树中都是连续的
如果不等,那么 我们选择deep较深的那个节点向较深的节点靠近,我们假设L为那个更深的节点. 更新(L, top[L]) 然后让L=top[L] ,
然后我们可以发现重复此过程可以把所有的点(或边更新完) 最多不会超过logn次
const int MAXN = 50010; struct Edge { int to,next; }edge[MAXN*2]; int head[MAXN],tot; int top[MAXN];//top[v]表示v所在的重链的顶端节点 int fa[MAXN]; //父亲节点 int deep[MAXN];//深度 int num[MAXN];//num[v]表示以v为根的子树的节点数 int p[MAXN];//p[v]表示点 在线段树中的位置 int fp[MAXN];//和p数组相反 即 记录线段树中的某个位置 存的是那个点int son[MAXN];//重儿子 int pos; // 每个点在线段树中的位置。int n; int a[MAXN]; int b[MAXN];void init() { tot = 0; memset(head,-1,sizeof(head)); pos = 1;//边的序号其实是从1开始? memset(son,-1,sizeof(son)); } void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++; } void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son { deep[u] = d; fa[u] = pre; num[u] = 1; for(int i = head[u];i != -1; i = edge[i].next) { int v = edge[i].to; //因为路径是双向的,所以不能等于父节点 if(v == pre) continue; dfs1(v,u,d+1); //更新u节点的儿子数目 num[u] += num[v]; if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v son[u] = v; } } //因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]] void getpos(int u,int sp) //第二遍dfs求出top和p { top[u] = sp; //先找重儿子 if(son[u] != -1) // 存在重儿子 { //把点的位置标记一下 p[u] = pos++; b[ p[u] ] = a[u]; // 原始点 u 的值为a[u] , u在线段树中的位置为p[u] ,那么 fp[p[u]] = u; //更新重儿子 getpos(son[u],sp); } //如果到了叶子节点 else { //不再向下dfs p[u] = pos++; b[ p[u] ] = a[u]; fp[p[u]] = u; return; } //更新其他的节点 for(int i = head[u] ; i != -1; i = edge[i].next) { int v = edge[i].to; if(v != son[u] && v != fa[u]) getpos(v,v); // 轻儿子的top[u]=u } } #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 struct seg{ int l,r; int lazy; int v;}st[MAXN<<2];void push_down(int rt){ // 这个pushdown 每次都写得很艰难啊 , 这样写的话 需要放在每一次更新和询问的第一行。 if(st[rt].lazy!=0 ){ // printf("pushdown :%d,%d ------%d\n",st[rt].l,st[rt].r, st[rt].lazy ); st[rt].v+=st[rt].lazy; if(st[rt].l!=st[rt].r){ st[rt<<1].lazy+=st[rt].lazy; st[rt<<1|1].lazy+=st[rt].lazy; } st[rt].lazy=0; }}void build(int l,int r,int rt){ st[rt].lazy=st[rt].v=0; st[rt].l=l; st[rt].r=r; if(l==r){ st[rt].v=b[l]; return; } int m=(l+r)>>1; build(lson); build(rson); } void update(int v,int L,int R,int l,int r,int rt){ // 区间更新 push_down(rt); if( L<=l &&r<=R){ // printf("up :%d %d %d\n",st[rt].l,st[rt].r ,v); st[rt].lazy+=v; return; } int m=(l+r)>>1; if(m>=L) update(v,L,R,lson); if(R>m) update(v,L,R,rson); } int query(int p,int l,int r,int rt){ // 单点询问 push_down(rt); if( l==r && l==p){ return st[rt].v; } // printf("qur:%d %d %d p=%d push%d\n",l,r,rt,p ,st[rt].lazy); int m=(l+r)>>1; if(p<=m) return query(p,lson); else return query(p,rson); } void _find(int u,int v,int k){ int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身 int temp=0; while(f1!=f2){ //从深度较深的开始查询 if(deep[f1]<deep[f2]){ swap(f1,f2); swap(u,v); } //查区间改变 update(k,p[f1],p[u],1,n,1); // u----top[u] 在dfs的时候显然top先放入线段树 u=fa[f1];f1=top[u]; } //如果f1=f2代表在同一条重链上m,如果u=v 那么也需要单点更新 // if(u==v) // return ; if(deep[u]>deep[v]) swap(u,v); update(k,p[u],p[v],1,n,1) ; // v-----u deep[u]小 那自然先放入线段树} char op[5];int main(){ int t; freopen("1.txt","r",stdin); int q,m; while(~scanf("%d %d %d",&n,&m,&q) ){ init(); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int u,v; for(int i=1;i<n;i++){ scanf("%d %d",&u,&v); addedge(u,v); addedge(v,u); } dfs1(1,0,1); getpos(1,1); build(1,n,1); int k; while(q--){ scanf("%s",op); if(op[0]=='I'){ //+ scanf("%d %d %d",&u,&v,&k); _find(u,v,k); } else if(op[0]=='D'){ // - scanf("%d %d %d",&u,&v,&k); _find(u,v,-1*k); } else{ scanf("%d",&u); printf("%d\n",query(p[u],1,n,1)); } // printf("\n\nq=%d\n",q); // for(int i=1;i<=n;i++){ // printf("i=%d p=%d %d\n",i,p[i],query(p[i],1,n,1)); // } } } return 0; }
wa一次是因为在_find里面 如果 u==v那么不会进行更新,我们也应该对其进行单点更新。顺便有更新了一波线段树push_down写法,(滑稽
3.BZOJ1036: [ZJOI2008]树的统计Count (对点进行操作)
也是裸的树链剖分,不过注意以下几个点:
1.有可能是负数,所以取maxx的时候ans初始值为很大的负数,不能为0
2.进行单点更新是 , 很容易写成更新u , 应该是更新p[u]
3.顺便记得 init ,先输入数组,在dfs,getpos ,build 等等
wa+2
const int MAXN = 30010; struct Edge { int to,next; }edge[MAXN*2]; int head[MAXN],tot; int top[MAXN];//top[v]表示v所在的重链的顶端节点 int fa[MAXN]; //父亲节点 int deep[MAXN];//深度 int num[MAXN];//num[v]表示以v为根的子树的节点数 int p[MAXN];//p[v]表示点 在线段树中的位置 int fp[MAXN];//和p数组相反 即 记录线段树中的某个位置 存的是那个点int son[MAXN];//重儿子 int pos; // 每个点在线段树中的位置。int n; // 记得 n一定要开全局 int a[MAXN]; // 原始点 u 的值为a[u] , u在线段树中的位置为p[u] int b[MAXN]; // 线段树 b[ p[u] ]=a[u];void init() { tot = 0; memset(head,-1,sizeof(head)); pos = 1;//边的序号其实是从1开始? memset(son,-1,sizeof(son)); } void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++; } void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son { deep[u] = d; fa[u] = pre; num[u] = 1; for(int i = head[u];i != -1; i = edge[i].next) { int v = edge[i].to; //因为路径是双向的,所以不能等于父节点 if(v == pre) continue; dfs1(v,u,d+1); //更新u节点的儿子数目 num[u] += num[v]; if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v son[u] = v; } } //因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]] void getpos(int u,int sp) //第二遍dfs求出top和p { top[u] = sp; //先找重儿子 if(son[u] != -1) // 存在重儿子 { //把点的位置标记一下 p[u] = pos++; b[ p[u] ] = a[u]; // 原始点 u 的值为a[u] , u在线段树中的位置为p[u] ,那么 fp[p[u]] = u; //更新重儿子 getpos(son[u],sp); } //如果到了叶子节点 else { //不再向下dfs p[u] = pos++; b[ p[u] ] = a[u]; fp[p[u]] = u; return; } //更新其他的节点 for(int i = head[u] ; i != -1; i = edge[i].next) { int v = edge[i].to; if(v != son[u] && v != fa[u]) getpos(v,v); // 轻儿子的top[u]=u } } #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 struct seg{ int l,r; int v; int maxx; // 最大值 int sum; // 权值之和}st[MAXN<<2];void push_up(int rt){ st[rt].maxx= max(st[rt<<1].maxx,st[rt<<1|1].maxx) ; st[rt].sum=st[rt<<1].sum + st[rt<<1|1].sum ;}void build(int l,int r,int rt){ st[rt].l=l; st[rt].r=r; st[rt].maxx=st[rt].v=0; if(l==r){ st[rt].v=st[rt].maxx=st[rt].sum=b[l]; // printf("l=%d %d\n",l,b[l]); return; } int m=(l+r)>>1; build(lson); build(rson); push_up(rt);} void update(int p, int v,int l,int r,int rt){ // 单点 if( l==r ){ // printf("up :%d %d %d\n",st[rt].l,st[rt].r ,v); st[rt].v=v; st[rt].sum=v; st[rt].maxx=v; return; } int m=(l+r)>>1; if(p<=m) update(p,v,lson); else update(p,v,rson); push_up(rt);} int query(int L,int R,int l,int r,int rt,int k){ // 区间 if( L <=l && r<=R){ // printf("qur:%d %d===%d %d\n",st[rt].l ,st[rt].r ,st[rt].maxx,st[rt].sum); if(k==1) return st[rt].maxx; else return st[rt].sum; } int m=(l+r)>>1; int ans; if(k==1) ans=-1000000000; else ans=0; if( m>=L) { // if(k==1) ans=max(ans,query(L,R,lson,k) ); else ans+=query(L,R,lson,k); } if(m<R){ if(k==1) ans=max(ans,query(L,R,rson,k) ); else ans+=query(L,R,rson,k); } push_up(rt); return ans;} void _find(int u,int v,int k){ // printf("\n\n %d %d %d\n",u,v,k); int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身 int temp=0; int ans; if(k==2) ans=0; else ans=-100000000; while(f1!=f2){ //从深度较深的开始查询 if(deep[f1]<deep[f2]){ swap(f1,f2); swap(u,v); } //查区间改变 // printf("qujian1: %d %d ----------%d %d ans:%d\n",u,f1,p[f1],p[u],ans); if(k==1) ans=max(ans,query(p[f1],p[u],1,n,1,k) ); // u----top[u] 在dfs的时候显然top先放入线段树 else ans+=query(p[f1],p[u],1,n,1,k) ; u=fa[f1];f1=top[u]; } //如果f1=f2代表在同一条重链上m,如果u=v 那么也需要单点更新 // if(u==v) // return ; if(deep[u]>deep[v]) swap(u,v); // printf("qujian2: %d %d ----------%d %d ans:%d\n",u,v,p[u],p[v],ans); if(k==1) ans=max(ans, query(p[u],p[v],1,n,1,k) ); // v-----u deep[u]小 那自然先放入线段树 else ans+=query(p[u],p[v],1,n,1,k); printf("%d\n",ans);} char op[10];int main(){ //freopen("1.txt","r",stdin); while(~scanf("%d",&n)){ init(); int u,v; for(int i=1;i<n;i++){ scanf("%d %d",&u,&v); addedge(u,v); addedge(v,u); } for(int i=1;i<=n;i++) scanf("%d",&a[i]); dfs1(1,0,1); getpos(1,1); build(1,n,1); int q; scanf("%d",&q); while(q--){ scanf("%s %d %d",op,&u,&v); if(op[0]=='C'){ update(p[u],v,1,n,1); // 更新不是 u 啊,写的也太随意了吧。 } else{ if(op[1]=='M'){ _find(u,v,1); } else{ _find(u,v,2); } } } }}
http://acm.fzu.edu.cn/problem.php?pid=2082
四、FOJ Problem 2082 过路费(对边进行操作)
没有什么难点,感觉对边的操作没有对点的操作那么熟练,
主要是需要牢记p[u]:代表的是 u与父亲节点连边在线段树中的位置,
因此加完边之后需要更新下val数组。 但是需要先 dfs 和getpos ,因为更新val 时需要用到p数组
#include <bits/stdc++.h>using namespace std;typedef long long ll; /* */ const int MAXN = 50010; struct Edge { int to,next; }edge[MAXN*2]; int head[MAXN],tot; int top[MAXN];//top[v]表示v所在的重链的顶端节点 int fa[MAXN]; //父亲节点 int deep[MAXN];//深度 int num[MAXN];//num[v]表示以v为根的子树的节点数 int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置 int fp[MAXN];//和p数组相反 即 记录线段树中的某个位置的边的起点?int son[MAXN];//重儿子 int pos; // 每条边在线段树中的位置。int n; int val[MAXN];void init() { tot = 0; memset(head,-1,sizeof(head)); pos = 1;//边的序号其实是从1开始? memset(son,-1,sizeof(son)); } void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++; } void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son { deep[u] = d; fa[u] = pre; num[u] = 1; for(int i = head[u];i != -1; i = edge[i].next) { int v = edge[i].to; //因为路径是双向的,所以不能等于父节点 if(v == pre) continue; dfs1(v,u,d+1); //更新u节点的儿子数目 num[u] += num[v]; if(son[u] == -1 || num[v] > num[son[u]])//求出重儿子即:所有儿子中size 最大的节点v son[u] = v; } } //因为对于轻儿子来说,top[u]=u,对于重儿子来说,如果son[v]!=-1,那么top[v]=top[son[v]] void getpos(int u,int sp) //第二遍dfs求出top和p { top[u] = sp; //先找重儿子 if(son[u] != -1) // 存在重儿子 { //把边的位置标记一下 p[u] = pos++; //fp相当于是p的反函数,即记录线段树中某个位置的边 的在树中的起点。 fp[p[u]] = u; //更新重儿子 getpos(son[u],sp); } //如果到了叶子节点 else { //不再向下dfs p[u] = pos++; fp[p[u]] = u; return; } //更新其他的节点 for(int i = head[u] ; i != -1; i = edge[i].next) { int v = edge[i].to; if(v != son[u] && v != fa[u]) getpos(v,v); // 轻儿子的top[u]=u } } #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 struct node{ int l,r; ll v; ll sum;}st[MAXN<<2];void pushup(int rt){ // if(st[rt].l != st[rt].r) st[rt].sum= st[rt<<1].sum + st[rt<<1|1].sum ;} void build(int l,int r,int rt){ st[rt].l=l; st[rt].r=r; st[rt].v=st[rt].sum=0; if(l==r){ // printf("bd:%d =%d\n",l,val[l]); st[rt].v=val[l]; st[rt].sum=val[l]; return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int p,ll x,int l,int r,int rt){ // 单点更新 if(l==r){ st[rt].v=x; st[rt].sum=x; return; } int m=(l+r)>>1; if(p<=m) update(p,x,lson); else update(p,x,rson); pushup(rt); } ll query(int L,int R,int l,int r,int rt){ // 区间询问 if( L<=l &&r<=R){ return st[rt].sum; } int m=(l+r)>>1; ll res=0; if(m>=L) res+= query(L,R,lson); if(R>m) res+= query(L,R,rson); pushup(rt); return res; }ll _find(int u,int v){ // 找 a-----b 中所有路径的最大值。 int f1=top[u],f2=top[v];//先找到两个端点的重链顶端节点,如果是轻儿子,就是它本身 ll temp=0; while(f1!=f2){ //从深度较深的开始查询 if(deep[f1]<deep[f2]){ swap(f1,f2); swap(u,v); } //查询一条重链 temp+=query(p[f1],p[u],1,n,1) ; u=fa[f1];f1=top[u]; } //如果f1=f2代表在同一条重链上m,如果u=v代表更新结束 ,这一步的时候 u从更深的地方跳上来,跳至同一重链, 如果u=v那么所有路径都走过一遍。 if(u==v) return temp; if(deep[u]>deep[v]) swap(u,v); return temp+query(p[son[u]],p[v],1,n,1); } ll e[MAXN][3]; int main(){ freopen("1.txt","r",stdin); int q; while(~scanf("%d %d",&n,&q)){ init(); for(int i=1;i<n;i++){ scanf("%d %d %lld",&e[i][0],&e[i][1],&e[i][2]); addedge(e[i][0],e[i][1]); addedge(e[i][1],e[i][0]); } dfs1(1,0,0); getpos(1,1); // 现在已经把树剖分完毕。 for(int i=1;i<n;i++){ if(deep[e[i][0]]<deep[e[i][1]]) // 调整每条路径的父亲,儿子关系。0>1 0为儿子1为父亲 swap(e[i][0],e[i][1]); val[ p[e[i][0]] ]=e[i][2]; // pi表示i和其父亲连边在线段树中的位置。 // printf("val i=%d %d =%d\n",i,p[e[i][0]] ,e[i][2]); } build(1,n,1); // 以val建树 int op; int u; ll v; while(q--){ scanf("%d %d %lld",&op,&u,&v); if(op==0) update(p[e[u][0]],v,1,n,1); else printf("%lld\n",_find(u,v)); } } return 0; }
- 树链剖分模板题(luogu3384 【模板】树链剖分)
- 树链剖分模板题
- HDU3966(树链剖分) 模板题
- 树链剖分模板题汇总
- 树链剖分模板题
- 【树链剖分模板】题
- bzoj1036树链剖分模板题
- bzoj2157: 旅游 树链剖分模板题
- BZOJ 1036 - 树链剖分 模板题
- FZU 2082(树链剖分模板题)
- 树链剖分模板题 [HAOI2015]T2
- hdu 3966 树链剖分 模板题
- 树链剖分详解及其模板题
- Hdu 3966 Aragorn's Story【树链剖分模板题】模板记录
- 树链剖分 模板
- 模板-树链剖分
- 树链剖分模板
- 树链剖分模板
- python使用自己封装的库
- 避免’sudo echo x >’ 时’Permission denied’ 甲: 示例 sudo echo a > 1.txt -bash: 1.txt: Permission denied 乙:
- JAVA正则表达
- redis中conf文件中的配置
- netty(十一)源码分析之ByteBuf 三
- 【树链剖分模板】题
- 【分页查询】Page如何做到分页查询
- linux查找文件以及文件夹命令
- matlab2c基础使用教程(实矩阵、复矩阵)
- 深度优先搜索练习之神奇的矩环
- ccf csp 有趣的数 动态规划
- RecyclerView多条源数据却只显示一条
- Kaggle入侵物种检测VGG16示例——基于Keras
- iOS常见问题归纳与解答