CodeForce 916E Jamie and Tree(线段树+分类讨论)

版权声明:本文为博主原创文章,转载请著名出处 http://blog.csdn.net/u013534123 https://blog.csdn.net/u013534123/article/details/79966232
E. Jamie and Tree
time limit per test:2.5 seconds
memory limit per test:256 megabytes
input:standard input
output:standard output

To your surprise, Jamie is the final boss! Ehehehe.

Jamie has given you a tree with n vertices, numbered from 1 to n. Initially, the root of the tree is the vertex with number 1. Also, each vertex has a value on it.

Jamie also gives you three types of queries on the tree:

1 v — Change the tree's root to vertex with number v.

2 u v x — For each vertex in the subtree of smallest size that contains u and v, add x to its value.

3 v — Find sum of values of vertices in the subtree of vertex with number v.

A subtree of vertex v is a set of vertices such that v lies on shortest path from this vertex to root of the tree. Pay attention that subtree of a vertex can change after changing the tree's root.

Show your strength in programming to Jamie by performing the queries accurately!

Input

The first line of input contains two space-separated integers n and q (1 ≤ n ≤ 105, 1 ≤ q ≤ 105) — the number of vertices in the tree and the number of queries to process respectively.

The second line contains n space-separated integers a1, a2, ..., an ( - 108 ≤ ai ≤ 108) — initial values of the vertices.

Next n - 1 lines contains two space-separated integers ui, vi (1 ≤ ui, vi ≤ n) describing edge between vertices ui and vi in the tree.

The following q lines describe the queries.

Each query has one of following formats depending on its type:

1 v (1 ≤ v ≤ n) for queries of the first type.

2 u v x (1 ≤ u, v ≤ n,  - 108 ≤ x ≤ 108) for queries of the second type.

3 v (1 ≤ v ≤ n) for queries of the third type.

All numbers in queries' descriptions are integers.

The queries must be carried out in the given order. It is guaranteed that the tree is valid.

Output

For each query of the third type, output the required answer. It is guaranteed that at least one query of the third type is given by Jamie.

Examples
Input
Copy
6 7
1 4 2 8 5 7
1 2
3 1
4 3
4 5
3 6
3 1
2 4 6 3
3 4
1 6
2 2 4 -5
1 4
3 3
Output
Copy
27
19
5
Input
Copy
4 6
4 3 5 6
1 2
2 3
3 4
3 1
1 3
2 2 4 3
1 1
2 2 4 -3
3 1
Output
Copy
18
21
Note

The following picture shows how the tree varies after the queries in the first sample.
                    

      大致题意:给你一棵树,每个节点上面有一个权值。然后有三种询问:1、换根;2、把点x、y在现有树情况下的LCA的子树的所有点权增加一个值;3、求某个节点在现有树情况下的点权和。
      很显然的一个线段树经典题目,首先求所有点的dfs序,然后根据dfs序建立线段树,可以动态维护子树的权值和。但是这题的关键在于换根。因为换根之后树的形态发生了改变,对应的子树情况也会发生改变,不能简单的用初始情况下的dfs序。但是我们显然也不能真的去换根。


         我们现在重新考虑一下换根之后,对于2和3操作如何处理。对于操作3,根据询问点的不同,我们可以分为两种情况。
                                                    
        在这个树中,root表示初始情况下的根,p表示换根之后新的根,a表示询问的点,显然这种情况下,换根对a不产生影响。直接按照初始的状态下输出子树权值和即可。

                                                    
        同理,在这种情况下,我们发现p变为根之后对a的子树产生了影响。而这种影响也很容易计算,考虑首先求整棵树所有的点权和,然后再减去不是所求的一部分。这个部分通过图中我们可以看到,就是b的子树的点权和。通俗点说,就是a与p路径中,最靠近a的一个节点。


        然后再考虑操作2。操作2由于还要求两个点在现有树情况下的LCA,所有情况可能会复杂一点。但是总的来说还是可以总结的。首先就是这个LCA。

                                                   
           在原树中,x、y的LCA是a但是在换根之后y是LCA。经过观察可以发现,在所有情况下,LCA都是lca(x,y)、lca(x,p)和lca(y,p)四个点中深度最大的点。
           当我们确定了LCA之后,剩下的修改操作就与操作2类似了。


       然后说说一些细节的地方。首先,每个点的子树的编号的点的范围根据dfs遍历时的dfs序确定,每个点对应记录一个st[]和ed[],表示以该点为根的子树中第一个和最后一个点的编号。其次,对于操作3中,减去的子树的确定,我们可以利用之前LCA求的dp倍增数组在logN时间内一步步确定。最后是在修改和查询的时候特殊判断对根的操作。具体见代码:
#include<bits/stdc++.h>
#define LL long long
#define N 100010
using namespace std;

int st[N],ed[N],Rank[N];
int n,q,cnt,root=1;

struct Segment_Tree
{
	LL a[N];
	struct node{LL lazy,sum;int l,r;} tree[N<<2];
	inline void push_up(int i){tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;}

	inline void build(int i,int l,int r)
	{
		tree[i]=node{0,0,l,r};
		if (l==r){tree[i].sum=a[Rank[l]];return;}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
		push_up(i);
	}

	inline void push_down(int i)
	{
		tree[i<<1].lazy+=tree[i].lazy;
		tree[i<<1|1].lazy+=tree[i].lazy;
		tree[i<<1].sum+=(tree[i<<1].r-tree[i<<1].l+1)*tree[i].lazy;
		tree[i<<1|1].sum+=(tree[i<<1|1].r-tree[i<<1|1].l+1)*tree[i].lazy;
		tree[i].lazy=0;
	}

	inline void update(int i,int l,int r,LL x)
	{
		if ((tree[i].l==l)&&(tree[i].r==r))
		{
			tree[i].sum+=(r-l+1)*x;
			tree[i].lazy+=x;
			return;
		}
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) update(i<<1,l,r,x);
		else if (mid<l) update(i<<1|1,l,r,x);
		else
		{
			update(i<<1,l,mid,x);
			update(i<<1|1,mid+1,r,x);
		}
		push_up(i);
	}

	inline LL getsum(int i,int l,int r)
	{
		if ((tree[i].l==l)&&(tree[i].r==r)) return tree[i].sum;
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) return getsum(i<<1,l,r);
		else if (mid<l) return getsum(i<<1|1,l,r);
		else return getsum(i<<1,l,mid)+getsum(i<<1|1,mid+1,r);
	}
} seg;

namespace LCA
{
    int dp[N][21],dep[N];
    vector<int> g[N];

    inline void dfs(int x,int fa)
    {
        for(int i=0;i<g[x].size();i++)
        {
            int y=g[x][i];
            if (y==fa) continue;
            dep[y]=dep[x]+1;
            dp[y][0]=x; dfs(y,x);
        }
    }

    void ST()
    {
        for(int j=0;j<19;j++)
            for(int i=1;i<=n;i++)
                dp[i][j+1]=dp[dp[i][j]][j];
    }

    inline int lca(int u,int v)
    {
        if (u==v) return u;
        if (dep[v]>dep[u]) swap(u,v);
        for(int i=18;i>=0;i--)
            if (dep[dp[u][i]]>=dep[v]) u=dp[u][i];
        if (u==v) return u;
        for(int i=18;i>=0;i--)
            if (dp[u][i]!=dp[v][i]) u=dp[u][i],v=dp[v][i];
        return dp[u][0];
    }
}

int getpos(int x,int y)
{
    using namespace LCA;
    for(int i=18;i>=0;i--)
    {
        if (dep[dp[y][i]]<=dep[x]) continue;
        y=dp[y][i];
    }
    return y;
}

void label(int x,int fa)
{
    st[x]=++cnt;
    Rank[cnt]=x;
    using namespace LCA;
    for(int i=0;i<g[x].size();i++)
    {
        int y=g[x][i];
        if (fa==y) continue;
        label(y,x);
    }
    ed[x]=cnt;
}

int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%lld",&seg.a[i]);
    using namespace LCA; dep[0]=-1;
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    label(1,0);
    dfs(1,0); ST();
    seg.build(1,1,n);
    while(q--)
    {
        int op,x,y,val;
        scanf("%d%d",&op,&x);
        if (op==1) root=x;
        if (op==2)
        {
            scanf("%d%d",&y,&val);
            int a=lca(x,y);
            int b=lca(x,root);
            int c=lca(y,root);
            if (dep[a]>dep[b]) x=a; else x=b;
            if (dep[c]>dep[x]) x=c; y=lca(x,root);
            if (y==x&&x!=root)
            {
                seg.update(1,1,n,val);
                int pos=getpos(x,root);
                seg.update(1,st[pos],ed[pos],-val);
            } else
            if (x!=root) seg.update(1,st[x],ed[x],val);
                    else seg.update(1,1,n,val);
        }
        if (op==3)
        {
            int LCA=lca(x,root);
            if (LCA==x&&x!=root)
            {
                LL a=seg.getsum(1,1,n);
                int pos=getpos(x,root);
                LL b=seg.getsum(1,st[pos],ed[pos]);
                printf("%lld\n",a-b);
            } else
            if (x!=root) printf("%lld\n",seg.getsum(1,st[x],ed[x]));
                    else printf("%lld\n",seg.getsum(1,1,n));

        }
    }
    return 0;
}

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页