树链剖分 FZU 2082

来源:互联网 发布:godaddy域名转到 vps 编辑:程序博客网 时间:2024/06/05 15:26
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 50005;
int val[maxn];
struct node
{
    int l,r;
    long long sum;
} xds[maxn<<2];
void build(int ID,int l,int r)
{
    xds[ID].l=l;
    xds[ID].r=r;
    if(r==l)
    {
        xds[ID].sum=val[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(ID<<1,l,mid);
    build(ID<<1|1,mid+1,r);
    xds[ID].sum=xds[ID<<1].sum+xds[ID<<1|1].sum;
}
long long query(int ID,int l,int r)
{
    if(xds[ID].l==l&&xds[ID].r==r)
        return xds[ID].sum;
    int mid=(xds[ID].l+xds[ID].r)>>1;
    if(r<=mid)
        return query(ID<<1,l,r);
    else if(l>mid)
        return query(ID<<1|1,l,r);
    else
        return query(ID<<1,l,mid)+query(ID<<1|1,mid+1,r);
}
void gengxin(int ID,int l,int v)
{
    if(xds[ID].l==l&&xds[ID].r==l)
    {
        xds[ID].sum=v;
        return ;
    }
    int mid=(xds[ID].l+xds[ID].r)>>1;
    if(l<=mid)
        gengxin(ID<<1,l,v);
    else
        gengxin(ID<<1|1,l,v);
    xds[ID].sum=xds[ID<<1].sum+xds[ID<<1|1].sum;
}
int id,ne,N,M;
int first[maxn],next[maxn<<1],top[maxn],idx[maxn],far[maxn],son[maxn],dep[maxn],cnt[maxn];
/*
first和next是邻接表数组,top存的是链的顶端节点,idx存的剖分后每个边在线段树中的位置,far【u】记录的是u节点的父亲节点
son[u]的重儿子节点,dep[u]记录的是u节点在树中的深度,cnt[u]记录的是u节点的所有节点(是所有节点哦)的个数包括自身节点的个数
*/
struct Edge//邻接表的存储结构体
{
    int u,v,w;
    void set(int u,int v,int w)
    {
        this->u=u;
        this->v=v;
        this->w=w;
    }
} ed[maxn<<1];
void add_Edge(int u,int v,int w)//邻接表加边
{
    ed[ne].set(u,v,w);
    next[ne]=first[u];
    first[u]=ne++;
}
void dfs(int u,int pre,int d)//递归求出,far,son,dep,cnt,u表示当前节点,pre表示u的父亲节点,d表示深度
{
    far[u]=pre;
    son[u]=0;
    dep[u]=d;
    cnt[u]=1;
    for(int i=first[u]; i+1; i=next[i])//遍历以u为根节点的子节点
    {
        int v=ed[i].v;
        if(v==far[u])//因为是无向图所以要把u的父亲节点排除外
            continue;
        dfs(v,u,d+1);
        cnt[u]+=cnt[v];
        if(cnt[son[u]]<cnt[v])//判断重儿子,也就是节点数最多的儿子
            son[u]=v;
    }
}
void dfs(int u,int root)//u代表当前节点,root代表根节点
{
    idx[u]=++id;//让在同一重链上的点在线段树种相邻
    top[u]=root;
    if(son[u]) dfs(son[u],root);//如果u有重儿子就,先递归重儿子,因为重儿子和根节点的顶端节点相同,还有要让同一重链上的节点相邻
    for(int i=first[u]; i+1; i=next[i])
    {
        int v=ed[i].v;
        if(v==far[u]||v==son[u])//当v不是u父亲节点也不是u的重儿子时就是u的轻儿子
            continue;
        dfs(v,v);//轻儿子的顶端节点就是自身
    }
}
void init()
{
    int u,v,w;
    id=ne=0;
    memset(first,-1,sizeof(first));
    for(int i=1; i<N; i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add_Edge(u,v,w);
        add_Edge(v,u,w);
    }
    dfs(1,0,0);
    dfs(1,1);
    for(int i=0; i<N-1; i++)//调整点来达到把边的花费存到深度较大的节点所在的线段树中的位置,比如说a-b-c相连用b存放a-b的花费,c存放b-c的花费
    {
        int t=i<<1;
        if(dep[ed[t].u]<dep[ed[t].v])
            swap(ed[t].u,ed[t].v);
        val[idx[ed[t].u]]=ed[t].w;
    }
    build(1,1,id);//建树
}
long long slove(int u,int v)
{//轻链的是一条边一条边的计算,重链是是一条链的计算
    int p=top[u],q=top[v];
    long long ret=0;
    while(p!=q)//p==q说明已经链的顶点已经是同一点
    {
        if(dep[p]<dep[q])//一直判断深浅的话就能很好的计算重链了,假如说一直从一点向另一点算的话就会就不能实现重链上一次性算完了,就如新浪博客上图的10-11的花费如果一直从10相11算的话树链剖分和不剖分就没有意义了
        {
            swap(p,q);
            swap(u,v);
        }
        ret+=query(1,idx[p],idx[u]);
        u=far[p];
        p=top[u];
    }
    if(u==v))//说明是轻链
        return ret;
    if(dep[u]>dep[v])//说明是重链且不是根节点,在重链上求一段的花费
        swap(u,v);
    ret+=query(1,idx[son[u]],idx[v]);
    return ret;
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        init();
        for(int i=0; i<M; i++)
        {
            int k,u,v;
            scanf("%d%d%d",&k,&u,&v);
            if(k)
                printf("%lld\n",slove(u,v));
            else
                gengxin(1,idx[ed[u*2-2].u],v);
        }
    }
    return 0;
}
1 0
原创粉丝点击