题意简述
我们在一个树上搞Gay,给定每个点的初始权值,支持三种操作:
- 单点加权值
- 子树加权值
- 求某个点到根的权值和
点数,操作数都 1 e 5 1e5 1e5,所有输入的数的最大值不会超过 1 e 6 1e6 1e6(不会爆 l o n g l o n g long\ long long long)
数据
输入
第一行两个正整数
n
,
m
n,m
n,m表示点数和操作数。
接下来一行
n
n
n个正整数表示每个点的初始点权。
接下来
n
−
1
n-1
n−1行每行两个正整数
u
,
v
u,v
u,v表示
u
u
u和
v
v
v之间有连边。
接下来
m
m
m行每行一个正整数
o
o
o,表示操作种类。如果
o
=
1
o=1
o=1,给定
x
,
a
x,a
x,a,表示点
x
x
x的权加
a
a
a。
o
=
2
o=2
o=2,给定
x
,
a
x,a
x,a,表示
x
x
x子树的权加
a
a
a
o
=
3
o=3
o=3,给定
x
x
x,询问
x
x
x到根的路径上的点权和。
输出
对于每个 o = 3 o=3 o=3的操作,输出答案。
样例
输入
5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3
输出
6
9
13
思路
当我们第一次看到这个题的时候,我们会想:这 t m ^{tm} tm搞什么,在树上操作?不管了,暴力模拟。。。然后看到数据。。。
接着就开始 f a n t a s i e s fantasies fantasies,如果这个树上的问题,能打到一个 序 列 \color{red}序列 序列上做,那不就好搞了。所以我们观察怎么打到序列上。想解决这个,就要发现子树的 连 续 性 \color{red}连续性 连续性。子树上的点编号肯定不能保证连续,但是,我们会发现,一整个子树在 D F S DFS DFS的过程中,一定是被连续遍历的。所以想了一个东西: D F S DFS DFS序。
如何实现这个东西呢?我们把这个树重新编号!建一个 D F S i d DFSid DFSid数组, D F S i d [ u ] DFSid[u] DFSid[u]表示点 u u u的新编号,由于是按 D F S DFS DFS的顺序做的编号,所以叫 D F S i d DFSid DFSid。但是这样还不行,因为我们只知道子树是连续的,并且从 D F S i d [ u ] DFSid[u] DFSid[u]开始,但连续多长呢?或者终点在哪?无从得知。。。
所以我们考虑把这个 D F S i d DFSid DFSid加一维 [ 0 / 1 ] [0/1] [0/1],我们在 D F S DFS DFS到点 u u u的时候记录一次,设为 D F S i d [ u ] [ 0 ] DFSid[u][0] DFSid[u][0],然后遍历完 u u u点要返回的时候,再记录一次,设为 D F S i d [ u ] [ 1 ] DFSid[u][1] DFSid[u][1],然后这样我们只要在子树加的时候知道区间的终点在 D F S [ u ] [ 1 ] DFS[u][1] DFS[u][1],这样就好记录了。(单点。。。还是说一下吧,只要把 D F S i d [ u ] [ 0 ] DFSid[u][0] DFSid[u][0]加一下即珂。但是为了 3 3 3操作,后面还会继续讲)
那么
3
3
3操作怎么办呢?一条链,虽然珂能
D
F
S
DFS
DFS的时候连续遍历,但是也不排除不连续的珂能。这。。。怎么办。。。
举个栗子吧。如下图:
(一个比较毒瘤的树。。。)
我们要查询点
17
17
17到根的权值和。我们会发现,那条链是怎么形成的?我们在
D
F
S
DFS
DFS遍历的时候,这个就相当于那些还没有出栈的点组成的。(什么是出栈了的点呢?就是那些完全遍历完,没有什么用了的点)。如果我们在查询和的时候,这些出栈的点能够被
抵
消
\color{red}抵消
抵消,那就爽了。珂是。。。如何抵消呢?
我们不是 D F S i d DFSid DFSid开了个 2 2 2么。。。那么,我们只要设一个 i o io io数组, i o [ u ] io[u] io[u]表示 D F S i d DFSid DFSid为 u u u的是进来( i i i)还是出来( o o o)。如果是进来的话,就设为 1 1 1,否则就设为 − 1 -1 −1。与此同时,我们在线段树上维护一个 f l a g flag flag(代码中记为 F F F),表示区间的系数。这个系数是由区间中 所 有 点 的 i o 值 相 加 而 得 \color{red}所有点的io值相加而得 所有点的io值相加而得(如果区间是单点,那么这个系数就是该点上的 i o io io值了)。这个系数是干嘛的呢?它让一些值 自 然 抵 消 \color{red}自然抵消 自然抵消,然后我们在实现区间加值的时候,设我们要加 x x x,并不是直接 S + = x ∗ ( R − L + 1 ) S+=x*(R-L+1) S+=x∗(R−L+1),而是 S + = x ∗ F S+=x*F S+=x∗F。然后由于有了 F F F系数,第三个操作就珂以直接输出前缀和。原因是这个前缀和中就已经包含抵消了。
当然,说一下
1
1
1操作和
2
2
2操作的改变。
1
1
1操作就是多一步,不仅要在
D
F
S
i
d
[
u
]
[
0
]
DFSid[u][0]
DFSid[u][0]位置上单点加值,在
D
F
S
i
d
[
u
]
[
1
]
DFSid[u][1]
DFSid[u][1]上也要单点加值。当然,因为我们加的时候乘了一个系数,在
D
F
S
i
d
[
u
]
[
1
]
DFSid[u][1]
DFSid[u][1]上加的那个实际上是加了个负的。
问题来了:操作
2
2
2要不要变?
答案来了:代码不用,因为函数变了。理由:还记不记得我们在线段树实现区间加值的函数里,改变了一下乘的系数,从原来的
(
R
−
l
+
1
)
(R-l+1)
(R−l+1)改成了
F
F
F,所以函数已经变了,代码就不用变了。(但是一定要明白操作二简单的代码背后在干什么,这个理解比较困难,建议自己模拟一下看看)
说了这么多,总结一下这个题怎么做:
- 处理好进入/出去的顺序 D F S i d DFSid DFSid数组,以及出入系数 i o io io数组。
- 在乘的时候,维护好 F F F系数。
- 对于第 3 3 3个操作,输出前缀和即珂。
- 对于第 1 , 2 1,2 1,2个操作,单点/区间加值即珂,不要忘了 1 1 1操作要多一步。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define int long long
#define N 1001000
class Graph//存图
{
public:
int head[N];
int EdgeCount;
struct Edge
{
int To,Label,Next;
}Ed[N];
void clear()
{
memset(Ed,-1,sizeof(Ed));
memset(head,-1,sizeof(head));
EdgeCount=0;
}
void AddEdge(int u,int v,int w)
{
++EdgeCount;
Ed[EdgeCount]=(Edge){v,w,head[u]};
head[u]=EdgeCount;
}
int Start(int u)
{
return head[u];
}
int To(int u)
{
return Ed[u].To;
}
int Label(int u)
{
return Ed[u].Label;
}
int Next(int u)
{
return Ed[u].Next;
}
}G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
int w[N],p[N],io[N];//原点权,乘上了系数的点权,出入系数
class SegmentTree
{
public:
struct node
{
int l,r;
int s,a,f;
}tree[N<<1];
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define S tree[index].s
#define A tree[index].a
#define F tree[index].f
#define lL tree[ls].l
#define lR tree[ls].r
#define lS tree[ls].s
#define lA tree[ls].a
#define lF tree[ls].f
#define rL tree[rs].l
#define rR tree[rs].r
#define rS tree[rs].s
#define rA tree[rs].a
#define rF tree[rs].f
void Update(int index)
{
S=lS+rS;
F=lF+rF;//由于F维护的是区间F自然抵消的和,所以这里要把F也加上
}
void BuildTree(int l,int r,int index)
{
L=l,R=r,S=A=0;
if (l==r)
{
S=p[L];
F=io[L];
return;//单点的情况
}
int mid=(l+r)>>1;
BuildTree(l,mid,ls);
BuildTree(mid+1,r,rs);
Update(index);
}
void AddOne(int x,int index)
{
S+=x*F;//注意这里的改变:不是S+=x*(R-L+1)
A+=x;
}
void PushDown(int index)
{
if (A)
{
AddOne(A,ls);
AddOne(A,rs);
A=0;
}
}//差不多的思路
void Add(int l,int r,int x,int index)
{
if (l>R or L>r) return;
if (l<=L and R<=r) return AddOne(x,index);
PushDown(index);
Add(l,r,x,ls);
Add(l,r,x,rs);
Update(index);
}//非常显然的代码
//对了说一下,AddOne由于是void类型,所以我们返回这个东西不会报错
int Query(int l,int r,int index)
{
if (l>R or L>r) return 0;
if (l<=L and R<=r) return S;
PushDown(index);
return Query(l,r,ls)+Query(l,r,rs);
}//也是非常显然的代码
#undef ls //index<<1
#undef rs //index<<1|1
#undef L //tree[index].l
#undef R //tree[index].r
#undef S //tree[index].s
#undef A //tree[index].a
#undef F //tree[index].f
#undef lL //tree[ls].l
#undef lR //tree[ls].r
#undef lS //tree[ls].s
#undef lA //tree[ls].a
#undef lF //tree[ls].f
#undef rL //tree[rs].l
#undef rR //tree[rs].r
#undef rS //tree[rs].s
#undef rA //tree[rs].a
#undef rF //tree[rs].f
}T;
int n,m;
int DFSid[N][2];int cnt=0;//cnt记录当前的DFS序用到了哪个点
void DFS(int u,int f)
{
DFSid[u][0]=++cnt;
p[DFSid[u][0]]=w[u];
io[cnt]=1;//进来了哦QωQ
for(int i=G.Start(u);~i;i=G.Next(i))
{
int v=G.To(i);
if (v!=f)
{
DFS(v,u);//继续搜
}
}
DFSid[u][1]=++cnt;
p[DFSid[u][1]]=-w[u];
io[cnt]=-1;//珂以出去了哦QωQ
}
void Query()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%lld",&w[i]);
}
G.clear();//写链式前向星不清图见比♂利了。。。
for(int i=1;i<n;++i)
{
int a,b;scanf("%lld%lld",&a,&b);
Add(a,b,1);
}
cnt=0;memset(DFSid,0,sizeof(DFSid));
DFS(1,-1);
T.BuildTree(1,(n<<1),1);
//千万记得!!!BuildTree在DFS后面!!!
for(int i=1;i<=m;++i)
{
int o,x,a;
scanf("%lld",&o);
if (o==1)
{
scanf("%lld%lld",&x,&a);
T.Add(DFSid[x][0],DFSid[x][0],a,1);
T.Add(DFSid[x][1],DFSid[x][1],a,1);
}
else if (o==2)
{
scanf("%lld%lld",&x,&a);
T.Add(DFSid[x][0],DFSid[x][1],a,1);
}
else
{
scanf("%lld",&x);
printf("%lld\n",T.Query(1,DFSid[x][0],1));
}
}//处理操作
}
void Main()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
Query();
}
#undef int //long long
#undef N //1001000
};
main()
{
Flandle_Scarlet::Main();
return 0;
}