CodeChef - DGCD——树链剖分+差分

【题目描述】

 You're given a tree on N vertices. Each vertex has a positive integer written on it, number on the ith vertex being vi. Your program must process two types of queries :

 1. Find query represented by F u v : Find out gcd of all numbers on the unique path between vertices u and v in the tree (both inclusive).

 2. Change query represented by C u v d : Add d to the number written on all vertices along the unique path between vertices u and v in the tree (both inclusive).

Input

First line of input contains an integer N denoting the size of the vertex set of the tree. Then follow N - 1 lines, ith of which contains two integers ai and bi denoting an edge between vertices ai and bi in the tree. After this follow N space separated integers in a single line denoting initial values vi at each of these nodes. Then follows a single integer Q on a line by itself, denoting the number of queries to follow. Then follow Q queries, each one on a line by itself. Each query is either a find query or a change query with format as given in problem statement. Note that all vertices are 0-based.

Output

For every find query, print the answer to that query in one line by itself.

Example

Input:

6
0 4
0 5
1 5
5 2
3 5
3 1 3 2 4 2
5
F 3 5
C 1 3 1
C 3 4 4
F 3 0
F 2 5

Output:

2
7
1

Constraints

1 <= N <= 50000
1 <= Q <= 50000
0 <= u, v <= N-1
1 <= vi <= 10^4
0 <= d <= 10^4 

【题目分析】
历经一天我终于搞出这道题了。其实用半天写了代码,但是另外半天都在调,因为用位操作符的时候少写了一个小于好导致数组越界发生玄学错误,我愣是研究了一下午加一晚上都没有发现,各种调试觉得是电脑出问题了,我明明没有修改那个数组的值但是他的值莫名其妙就改了。。。
今天早上发现这个错误后直接A了。
自己看的时候还是不会,觉得就算用树链剖分用线段树维护区间GCD可是人家还有区间修改怎么办,总不能区间修改以后暴力维护一遍区间GCD吧。学习了一下其他大佬的博客后发现一个比较有(xuan)趣(xue)的结论: g c d ( x 1 , x 2 , x 3... ) = g c d ( x 1 , x 2 − x 1 , x 3 − x 2 , . . . ) gcd(x1,x2,x3...)=gcd(x1,x2-x1,x3-x2,...) gcd(x1,x2,x3...)=gcd(x1,x2x1,x3x2,...)
然后我们就发现区间GCD和差分结合在一起啦啦啦啦。对于每一个区间,我们进行一次单点查询x1,再进行一次区间查询后面的维护的GCD。即 g c d ( x 1 , x 2 , x 3... ) = g c d ( v a l 1 [ x 1 ] , v a l 2 [ x 2.. x n ] ) gcd(x1,x2,x3...)=gcd(val1[x1],val2[x2..xn]) gcd(x1,x2,x3...)=gcd(val1[x1],val2[x2..xn]),其中val2维护的是差分区间的GCD值,val1维护的是原来的数值。
这样做的意义就是让区间修改变成单点修改:对于每次区间修改,我们发现对于差分区间内部来讲其实只改变了第一个的值,其他的都没有改变(大家都增加了,减掉后值就没有改变)。
为了每次方便查询,我们用两个树链分别维护原来的值和差分的值,查询的时候分别对两个进行单点查询和区间查询。
每次修改我们对原来的值的树链进行区间修改,对维护差分GCD的树链进行单点修改:这个和我们进行储存的方式息息相关,在这里我们是从重链往下,将儿子节点的值进行差分(父亲节点-儿子节点)(理解这个十分重要)。因此如果我们修改一个区间,重链的头节点的值会增加val(因为没有什么减他),修改的区间在这条链的最后的节点的儿子节点(如果有的话)会增加val(因为最后的节点的值增加了,儿子节点的值为最后节点减去儿子节点),如果区间没有到达重链的头节点,那么区间最前面的点的值会减小val(因为他的值应该是父亲节点减去他,他的值增加而父亲节点的值没有变化所以他的值要减小val)。可能一开始还是有些难以理解(我理解了半天),可以对照着代码。代码应该还是比较清晰的。(注意区间修改是val1区间,单点修改是val2区间,区间查询是val2区间,单点查询是val1区间)
【AC代码】

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<climits>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;

typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=5e4+5;
int head[MAXN],fa[MAXN],pos[MAXN],A[MAXN];
int a[MAXN],sz[MAXN],son[MAXN],deep[MAXN],top[MAXN];
struct node
{
    int v,next;
}Edge[MAXN<<1];
int tot=0,cnt=0;
int n,m;
int val1[MAXN<<2],add[MAXN<<2],val2[MAXN<<2];

void AddEdge(int u,int v)
{
    tot++;
    Edge[tot].v=v; Edge[tot].next=head[u];
    head[u]=tot;
}

void dfs1(int u,int f)
{
    fa[u]=f; deep[u]=deep[f]+1;
    son[u]=0; sz[u]=1;
    for(int i=head[u];i;i=Edge[i].next)
    {
        int v=Edge[i].v;
        if(v!=f)
        {
            dfs1(v,u);
            sz[u]+=sz[v];
            if(sz[son[u]]<sz[v]) son[u]=v;
        }
    }
}

void dfs2(int u,int f,int k)
{
    top[u]=k; pos[u]=++cnt; A[cnt]=a[u];
    if(!son[u]) return;
    if(son[u]) dfs2(son[u],u,k);
    for(int i=head[u];i;i=Edge[i].next)
    {
        int v=Edge[i].v;
        if(v!=f && v!=son[u]) dfs2(v,u,v);
    }
}

void test()
{
    for(int i=1;i<13;i++)
    {
        printf("%d ",add[i]);
    }
    printf("%d\n",add[13]);
}

void Build(int k,int l,int r)
{
    add[k]=0;
    if(l==r)
    {
        val1[k]=A[l];
        return;
    }
    int mid=(l+r)>>1;
    Build(k<<1,l,mid);
    Build(k<<1|1,mid+1,r);
}

int gcd(int a,int b)
{
    return b?gcd(b,a%b):abs(a);
}

void change_point(int k,int l,int r,int x,int val)
{
    if(l==r)
    {
        val2[k]+=val;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) change_point(k<<1,l,mid,x,val);
    else change_point(k<<1|1,mid+1,r,x,val);
    val2[k]=gcd(val2[k<<1],val2[k<<1|1]);
}

void pushdown(int k)
{
    if(add[k])
    {
        add[k<<1]+=add[k]; add[k<<1|1]+=add[k];
        val1[k<<1]+=add[k]; val1[k<<1|1]+=add[k];
        add[k]=0;
    }
}

void change_interval(int k,int l,int r,int L,int R,int val)
{
    if(l>=L && r<=R)
    {
        add[k]+=val;
        val1[k]+=val;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(k);
    if(L<=mid) change_interval(k<<1,l,mid,L,R,val);
    if(R>mid) change_interval(k<<1|1,mid+1,r,L,R,val);
}

void change_tree(int u,int v,int w)
{
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]]) swap(u,v);
        change_interval(1,1,n,pos[top[u]],pos[u],w);
        if(son[u]) change_point(1,1,n,pos[son[u]],w);
        u=fa[top[u]];
    }
    if(deep[u]<deep[v]) swap(u,v);
    change_interval(1,1,n,pos[v],pos[u],w);
    if(son[u]) change_point(1,1,n,pos[son[u]],w);
    if(son[fa[v]]==v) change_point(1,1,n,pos[v],-w);
}

int query_interval(int k,int l,int r,int L,int R)
{
    if(l>=L && r<=R)
    {
        return val2[k];
    }
    int mid=(l+r)>>1;
    if(R<=mid) return query_interval(k<<1,l,mid,L,R);
    else if(L>mid) return query_interval(k<<1|1,mid+1,r,L,R);
    else return gcd(query_interval(k<<1,l,mid,L,mid),query_interval(k<<1|1,mid+1,r,mid+1,R));
}

int query_point(int k,int l,int r,int x)
{
    if(l==r && l==x)
    {
        return val1[k];
    }
    int mid=(l+r)>>1;
    pushdown(k);
    if(x<=mid) return query_point(k<<1,l,mid,x);
    else return query_point(k<<1|1,mid+1,r,x);
}

int query_tree(int u,int v)
{
    int ret=0;
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]]) swap(u,v);
        if(top[u]!=u) ret=gcd(ret,query_interval(1,1,n,pos[son[top[u]]],pos[u]));
        ret=gcd(ret,query_point(1,1,n,pos[top[u]]));
        u=fa[top[u]];
    }
    if(deep[u]<deep[v]) swap(u,v);
    if(u!=v) ret=gcd(ret,query_interval(1,1,n,pos[son[v]],pos[u]));
    ret=gcd(ret,query_point(1,1,n,pos[v]));
    return ret;
}

void Read()
{
    int u,v;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        AddEdge(u+1,v+1); AddEdge(v+1,u+1);
    }
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    // test();
}

void Init()
{
    dfs1(1,0); dfs2(1,0,1);
    Build(1,1,n);
    for(int i=1;i<=n;i++)
    {
        if(son[i])
        change_point(1,1,n,pos[son[i]],a[i]-a[son[i]]);
    }
}


void Solve()
{
    char cmd[10];
    int u,v,w;
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",cmd);
        if(cmd[0]=='F')
        {
            scanf("%d%d",&u,&v);
            printf("%d\n",query_tree(u+1,v+1));
        }
        else
        {
            scanf("%d%d%d",&u,&v,&w);
            change_tree(u+1,v+1,w);
        }
    }
}



int main()
{
    Read();
    Init();
    Solve();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值