ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树链割分原理和实现

2019-09-02 15:44:08  阅读:207  来源: 互联网

标签:重链 int dfs son 割分 maxn 树链 原理 include


树链割分即将树分割成为一段一段的线段区间,具体分割的过程就是一个dfs的过程,然后就可以将树结构划分为数组结构,从而实现树上的区间操作,即用线段树或者树状数组处理。

首先就是一些必须知道的概念:

  • 重结点:子树结点数目最多的结点;
  • 轻节点:父亲节点中除了重结点以外的结点;
  • 重边:父亲结点和重结点连成的边;
  • 轻边:父亲节点和轻节点连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

fa[u]:保存结点u的父亲节点

dep[u]:保存结点u的深度值

siz[u]:保存以u为根的子树节点个数

son[u]:保存重儿子

rnk[u]:保存当前dfs标号在树中所对应的节点

top[u]:保存当前节点所在链的顶端节点

tid[u]:保存树中每个节点剖分以后的新编号(DFS的执行顺序)

树链剖分的两个性质:

1,如果(u, v)是一条轻边,那么size(v) < size(u)/2;

2,从根结点到任意结点的路所经过的轻重链的个数必定都小于logn;

可以证明,树链剖分的时间复杂度为O(nlog^2n)

 

点权的树链剖分

两次dfs用于获取带最长链的树链:

vector<int> tr[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree编号转dfs编号
int rnk[maxn];   //dfs编号转tree编号
int dfsnum;

void dfs1(int u,int father,int depth)    //当前节点、父节点、层次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //这个点本身size=1
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i];
        if(to==father)
            continue;
        dfs1(to,u,depth+1);    //层次深度+1
        sz[u]+=sz[to];    //子节点的size已被处理,用它来更新父节点的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //选取size最大的作为重儿子
    }
}

void dfs2(int u,int t)    //当前节点、重链顶端
{
    top[u]=t;
    tid[u]=++dfsnum;    //标记dfs序
    rnk[dfsnum]=u;    //序号cnt对应节点u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
    一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i];
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一个点位于轻链底端,那么它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

由于对于点的存储vector只需要存储int,因此对于边的优化影响不大

边存储优化(完整的最优化树链剖分 点权)


struct edge
{
    int to;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree编号转dfs编号
int rnk[maxn];   //dfs编号转tree编号
int dfsnum;

void dfs1(int u,int father,int depth)    //当前节点、父节点、层次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //这个点本身size=1
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to==father)
            continue;
        dfs1(to,u,depth+1);    //层次深度+1
        sz[u]+=sz[to];    //子节点的size已被处理,用它来更新父节点的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //选取size最大的作为重儿子
    }
}

void dfs2(int u,int t)    //当前节点、重链顶端
{
    top[u]=t;
    tid[u]=++dfsnum;    //标记dfs序
    rnk[dfsnum]=u;    //序号cnt对应节点u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
    一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一个点位于轻链底端,那么它的top必然是它本身
    }
}
void treeSplit(int root)
{
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

如果不用需要最长链,可以修改成一次dfs(该方法会加长后续操作时间)

vector<int> tree[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int son[maxn];
int tid[maxn];   //tree编号转dfs编号
int rnk[maxn];   //dfs编号转tree编号
int dfsnum=0;
void init()
{
    dfsnum=0;
    memset(son,-1,sizeof(son));
}

//以上数组,并不是全部都需要,按照题目要求,可以用上述数组化简操作的时候才进行记录
void dfs(int u,int t,int father,int depth)
{
    //u: 当前结点  t:该链的top结点 father: 父亲结点   depth: 深度
    top[u]=t;
    fa[u]=father;
    dep[u]=depth;

    tid[u]=++dfsnum;
    rnk[tid[u]]=u;

    bool first=true;
    for(int i=0;i<tree[u].size();i++)
    {
        int to=tree[u][i];
        if(to!=fa[u])
        {
            if(first)
            {
                son[u]=to;
                dfs(to,t,u,depth + 1);
                first=false;
            }
            else
                dfs(to,to,u,depth + 1);
        }
    }
}

int main()
{
    dfs(root,root,-1,1);
}

边权的树链剖分

int num[maxn];
struct edge
{
    int to;
    int w;
    int num;
    edge(){}
    edge(int a,int b,int c){to=a;w=b;num=c;}
};
vector<edge> tr[maxn];
int eMapDfs[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree编号转dfs编号
int rnk[maxn];   //dfs编号转tree编号
int dfsnum;

void dfs1(int u,int father,int depth)    //当前节点、父节点、层次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //这个点本身size=1
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i].to;
        if(to==father)
            continue;
        num[to]=tr[u][i].w;    //承认to是u的儿子了,将边权下放
        eMapDfs[tr[u][i].num]=to;   //当前边的编号对应的tree树的编号
        dfs1(to,u,depth+1);    //层次深度+1
        sz[u]+=sz[to];    //子节点的size已被处理,用它来更新父节点的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //选取size最大的作为重儿子
    }
}

void dfs2(int u,int t)    //当前节点、重链顶端
{
    top[u]=t;
    tid[u]=++dfsnum;    //标记dfs序
    rnk[dfsnum]=u;    //序号cnt对应节点u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
    一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一个点位于轻链底端,那么它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(num,0,sizeof(num));
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

边vector的存储优化

(完整的最优化树链剖分 边权)

struct edge
{
    int id;
    int to;
    int w;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w,int id)
{
    e[tot].to=v;
    e[tot].next=head[u];
    e[tot].id=id;
    e[tot].w=w;
    head[u]=tot;
    tot++;
}
int num[maxn];
int eMapDfs[maxn];     //边序号转该边对应的权值下放的树节点编号
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];          //用于处理树链剖分的长链短链
int son[maxn];
int tid[maxn];   //tree编号转dfs编号
int rnk[maxn];   //dfs编号转tree编号
int dfsnum;
void dfs1(int u,int father,int depth)    //当前节点、父节点、层次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //这个点本身size=1
    for(int i=head[u];i!=-1;i=e[i].next)
    {       //当前边为e[i]
        int to=e[i].to;
        if(to==father)
            continue;
        num[to]=e[i].w;
        eMapDfs[e[i].id]=to;
        dfs1(to,u,depth+1);    //层次深度+1
        sz[u]+=sz[to];    //子节点的size已被处理,用它来更新父节点的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //选取size最大的作为重儿子
    }
}
void dfs2(int u,int t)    //当前节点、重链顶端
{
    top[u]=t;
    tid[u]=++dfsnum;    //标记dfs序
    rnk[dfsnum]=u;    //序号cnt对应节点u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
    一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一个点位于轻链底端,那么它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(num,0,sizeof(num));
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

当树链构造成功之后,我们就可以利用线段树来维护树上的信息,来处理某点子树和到根路径的信息。

 

通过dfs我们已经保证一条重链上各个节点dfs序连续,那么可以想到,我们可以通过数据结构(以线段树为例)来维护一条重链的信息
回顾上文的那个题目,修改和查询操作原理是类似的,以查询操作为例,其实就是个LCA,不过这里使用了top来进行加速,因为top可以直接跳转到该重链的起始结点,轻链没有起始结点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到top的位置,避免两个一起跳从而插肩而过。

 

树链构建线段树:

❤注意:由于线段树是跟随dfs序号构建的,因此需要用tid将树编号转化成dfs编号(下例)❤

ll sum[maxn<<2];   //随其他修改
void build(int l,int r,int pos)   //k代表了挂该数据存储位置在第几个数组中,l,r代表该位置数组覆盖范围
{
    if(l==r)
    {
        sum[pos]=num[rnk[l]];     //dfs编号转换成tree编号,在转换成树上val值
        return;
    }
    int mid=(l+r)/2;

    build(l,mid,pos<<1);
    build(mid+1,r,pos<<1|1);
    sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}

当线段树构建完成后,就可以在线段树上进行一系列的修改与查询过程了。具体的修改与查询与具体的操作有关

图与树的dfs:https://blog.csdn.net/qq_38890926/article/details/81222698

 

树链路径

求取结点u到v路径上链的值(值在点上)

ll getTreeLine(int u,int v)   //传入树编号
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);

        ans=ans+query(1,n,1,tid[top[u]],tid[u]);
        u=fa[top[u]];
    }

    if(dep[u]>dep[v]) swap(u,v);
    ans=ans+query(1,n,1,tid[u],tid[v]);

    return ans;
}

求取结点u到v路径上链的值(值在边上)

ll getTreeLine(int u,int v)   //传入树编号
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);

        ans=ans+query(1,n,1,tid[top[u]],tid[u]);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans=ans+query(1,n,1,tid[u]+1,tid[v]);

    return ans;
}

 

 

点权,单点修改区间查询

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>

#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;


inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}

int n;
struct edge
{
    int to;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int a[maxn];
struct TreeSplit
{
    int tp[maxn],fa[maxn],d[maxn],son[maxn];//关心链上信息
    int tid[maxn],rnk[maxn];//tr转dfs,dfs转tr
    int dfn,sz[maxn];
    void dfs1(int u,int f,int dep)
    {
        fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;if(to==f)continue;
            dfs1(to,u,dep+1);
            sz[u]=sz[u]+sz[to];
            if(sz[to]>sz[son[u]])son[u]=to;//重儿子size最大
        }
    }
    void dfs2(int u,int t)//当前点,重链顶
    {
        tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
        if(!son[u])return;
        dfs2(son[u],t);//先重儿子保证重链dfs序连续
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;
            if(to!=son[u]&&to!=fa[u])dfs2(to,to);//点在轻链底端,top是它自己
        }
    }
    void treeSplit(int rt)
    {
        dfn=0;
        dfs1(rt,-1,0);dfs2(rt,rt);
    }
    ll getTreeLinemax(int u,int v);
    ll getTreeLinesum(int u,int v);
}tree;
struct node
{
    int l,r;
    ll sum;
    ll maxnum;
};
struct SegmentTree //单点修改,区间查询
{
    node tr[maxn<<2];
    inline void buildInit(int p)
    {
        tr[p].maxnum=tr[p].sum=a[tree.rnk[tr[p].l]];
    }
    inline void push_up(int p)
    {
        tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
        tr[p].maxnum=max(tr[p<<1].maxnum,tr[p<<1|1].maxnum);
    }
    inline void changeOp(int p,ll v)
    {
        tr[p].maxnum=tr[p].sum=v;
    }
    inline node queryUnion(node a,node b)
    {
        node tmp;
        tmp.sum=a.sum+b.sum;
        tmp.maxnum=max(a.maxnum,b.maxnum);
        return tmp;
    }
    void build(int l,int r,int p)
    {
        tr[p].l=l,tr[p].r=r;
        if(l==r){buildInit(p);return;}
        int mid=(l+r)>>1;
        build(l,mid,p<<1);build(mid+1,r,p<<1|1);
        push_up(p);
    }
    void change(int p,int t,ll v)
    {
        if(tr[p].l==tr[p].r){changeOp(p,v);return;}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(t<=mid) change(p<<1,t,v);
        else change(p<<1|1,t,v);
        push_up(p);
    }
    node query(int p,int lt,int rt)
    {
        if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(rt<=mid) return query(p<<1,lt,rt);
        if(lt>mid) return query(p<<1|1,lt,rt);
        return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
    }
}st;

ll TreeSplit::getTreeLinemax(int u,int v)   //传入树编号
{
    ll ans=-inf;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=max(ans,st.query(1,tid[tp[u]],tid[u]).maxnum);
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=max(ans,st.query(1,tid[v],tid[u]).maxnum);
    return ans;
}
ll TreeSplit::getTreeLinesum(int u,int v)   //传入树编号
{
    ll ans=0;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=ans+st.query(1,tid[tp[u]],tid[u]).sum;
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=ans+st.query(1,tid[v],tid[u]).sum;
    return ans;
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(int i=1;i<n;i++)
        {
            int u,v;scanf("%d %d",&u,&v);
            addedge(u,v);addedge(v,u);
        }
        tree.treeSplit(1);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        st.build(1,n,1);
        int m;scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            char op[10];int x,y;scanf("%s %d %d",op,&x,&y);
            if(op[3]=='X')printf("%lld\n",tree.getTreeLinemax(x,y));
            if(op[3]=='M')printf("%lld\n",tree.getTreeLinesum(x,y));
            if(op[3]=='N')st.change(1,tree.tid[x],y);
        }
    }
    return 0;
}

边权,区间查询

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>

#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e9+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;


inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}

int n,q;
struct edge
{
    int to;
    int w;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
    e[tot].to=v;
    e[tot].w=w;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int a[maxn];
struct TreeSplit
{
    int tp[maxn],fa[maxn],d[maxn],son[maxn];//关心链上信息
    int tid[maxn],rnk[maxn];//tr转dfs,dfs转tr
    int dfn,sz[maxn];
    void dfs1(int u,int f,int dep)
    {
        fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;if(to==f)continue;
            a[to]=e[i].w;
            dfs1(to,u,dep+1);
            sz[u]=sz[u]+sz[to];
            if(sz[to]>sz[son[u]])son[u]=to;//重儿子size最大
        }
    }
    void dfs2(int u,int t)//当前点,重链顶
    {
        tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
        if(!son[u])return;
        dfs2(son[u],t);//先重儿子保证重链dfs序连续
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;
            if(to!=son[u]&&to!=fa[u])dfs2(to,to);//点在轻链底端,top是它自己
        }
    }
    void treeSplit(int rt)
    {
        dfn=0;
        dfs1(rt,-1,0);dfs2(rt,rt);
    }
    ll getTreeLinemin(int u,int v);
}tree;

struct node
{
    int l,r;
    ll minnum;
    node(){}
    node(ll x){minnum=x;}
};
struct SegmentTree //单点修改,区间查询
{
    node tr[maxn<<2];
    inline void buildInit(int p)
    {
        tr[p].minnum=a[tree.rnk[tr[p].l]];
    }
    inline void push_up(int p)
    {
        tr[p].minnum=min(tr[p<<1].minnum,tr[p<<1|1].minnum);
    }
    inline node queryUnion(node a,node b)
    {
        node tmp;
        tmp.minnum=min(a.minnum,b.minnum);
        return tmp;
    }
    void build(int l,int r,int p)
    {
        tr[p].l=l,tr[p].r=r;
        if(l==r){buildInit(p);return;}
        int mid=(l+r)>>1;
        build(l,mid,p<<1);build(mid+1,r,p<<1|1);
        push_up(p);
    }
    node query(int p,int lt,int rt)
    {
        if(lt>rt)return node(inf);
        if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(rt<=mid) return query(p<<1,lt,rt);
        if(lt>mid) return query(p<<1|1,lt,rt);
        return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
    }
}st;


ll TreeSplit::getTreeLinemin(int u,int v)   //传入树编号
{
    ll ans=inf;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=min(ans,st.query(1,tid[tp[u]],tid[u]).minnum);
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=min(ans,st.query(1,tid[v]+1,tid[u]).minnum);
    return ans;
}

int main()
{
    cin>>n>>q;
    init();
    for(int i=1;i<n;i++)
    {
        int f,t,w;cin>>f>>t>>w;
        addedge(f,t,w);addedge(t,f,w);
    }
    memset(a,0,sizeof(a));
    tree.treeSplit(1);
    st.build(1,n,1);
    while(q--)
    {
        int f,t;cin>>f>>t;
        cout<<tree.getTreeLinemin(f,t)<<endl;
    }
    return 0;
}

标签:重链,int,dfs,son,割分,maxn,树链,原理,include
来源: https://blog.csdn.net/qq_38890926/article/details/89380139

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有