前言(瞎唠嗑)
确实从ACM集训队退役之后,有大半年的时间没有刷题了。不知道之后是推免读研还是去找工作(不想自己考)。万一推免不成功呢,我还是得乖乖滴去找工作的。所以呢,我就打开了招聘网站,看了一下字节跳动的岗位要求,哇瑟,那就是得算法数据结构者,得字节offer呀。没错我决定重新刷题,谁叫刷题使我快乐呢(
痛并快乐)。好了,废话不多说了,开始今天题目的复盘。
题目描述
我们的李华去当交警啦!
交通网络呈树状(即n个点,n-1条边),1为根节点,某个时刻,李华可能会接到联络:
1:联络称某两点之间的道路发生堵塞,使得他们到达这两点之间的点的时间会增加
2:他们接到了一个紧急任务需要处理,即会在树的某个节点下方增加一个点
3:他们任务栏中的一个任务已经被完成了,即会删除树的某个节点(保证图始终连通)输入描述:
第一行输入两个数n,m,表示点的个数和接到联络的个数
第二行输入n个数,表示初始到达第i个点的时间
接下来n-1行,每行两个数x,y表示x,y之间有一条边
接下来m行每行一个联络,具体如下
操作 1: 格式:1 x y k 含义:将x到y的路径上的点每个点的到达时间增加k
操作 2: 格式:2 x y含义:增加一个点,设这是第q次2操作,那么他的编号为n+q,他的初始到达时间为x,他的父亲为y
操作 3: 格式:3 x 含义:删除点x
特别提醒,由于栈大小的原因,请使用迭代层数较小的遍历方式输出描述:
输出若干行,按序号升序排序
每行第一个数表示该点序号,第二个数表示到达当前点需要的时间(只输出目前未被删除的点)
输入
6 4
0 0 0 0 0 0
1 2
1 3
2 4
2 5
5 6
1 4 6 1
2 0 5
1 4 7 1
3 6输出
1 0
2 2
3 0
4 2
5 2
7 1
思路:
- 这个题要用到LCA算法求最近的公共祖先(不会的可以自行百度,代码中用的是倍增法求解),需要用到一个数组存需要增加的时间,代码中用的zz数组。最终时间的数组代码中用的ans数组。
- 对于操作一,我们只需要zz[x]+=k,和zz[y]+=k。x,y的最近公共祖先为f,zz[f]-=k,如果f有父亲,那么zz[f父亲]-=k。然后进行DFS,使得每一个节点需要增加的时间等于所有子节点需要增加的时间之和(至于为什么是这样,举个例子,自己推一下就出来了)。
- 对于操作二,只需要将对应的边加上,并且,假设增加的节点编号为n+q,那么ans[n+q]+=x。
- 对于操作三,只需要用一个数组记一下哪些点被删除了,输出的时候判断一下即可。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=2e6+10;
const int M=5e5+10;
int n,m;
ll ans[N];
int to[N],edg,head[N],nex[N];
int opt[M],x[M],y[M],k[M];
int dep[N],fa[N][20];
ll mz[N];
int del[N];
ll zz[N];
void add(int s,int e)//增加一条边
{
to[++edg]=e;
nex[edg]=head[s];
head[s]=edg;
}
void dfs(int u,int f)
{
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1; 1<<i<dep[u]; i++)
fa[u][i]=fa[fa[u][i-1]][i-1];//fa[u][i]表示节点u的第2^i个祖先
for(int i=head[u]; i; i=nex[i])
{
int v=to[i];
if(v==f)
continue;
dfs(v,u);
}
}
int lca(int a,int b)
{
if(dep[a]>dep[b])//b的深度>a的深度
swap(a,b);
while(dep[a]<dep[b])//一直到a的深度=b的深度
{
b=fa[b][mz[dep[b]-dep[a]]];
}
if(a==b)
return a;
for(int i=mz[dep[b]]; i>=0; i--)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[b][0];
}
void dfs1(int u,int f)//求所有子节点之和
{
for(int i=head[u]; i; i=nex[i])
{
int v=to[i];
if(v==f)
continue;
dfs1(v,u);
zz[u]+=zz[v];
}
ans[u]+=zz[u];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%lld",&ans[i]);
for(int i=1; i<n; i++)
{
int s,e;
scanf("%d%d",&s,&e);
add(s,e);
add(e,s);
}
for(int i=1; i<=m; i++)//这里不能对操作一进行处理,只能先将输入的数存起来,因为倍增法中需要计算每个节点的深度。
{//因为操作二会增加新的边,如果在这里对操作一进行操作,那么新增的点的深度就会出现错误,
scanf("%d",&opt[i]);
if(opt[i]==1)
scanf("%d%d%d",&x[i],&y[i],&k[i]);
else if(opt[i]==2)
{
scanf("%d%d",&x[i],&y[i]);
add(++n,y[i]);
add(y[i],n);
ans[n]+=x[i];
}
else
{
scanf("%d",&x[i]);
del[x[i]]=1;
}
}
for(int i=1; i<N; i++)
mz[i]=mz[i-1]+(i==1<<mz[i-1]);
for(int i=1; i<N; i++)
mz[i]-=1;
dfs(1,0);
for(int i=1; i<=m; i++)
{
if(opt[i]==1)
{
zz[x[i]]+=k[i];
zz[y[i]]+=k[i];
int f=lca(x[i],y[i]);
zz[f]-=k[i];
if(fa[f][0])
zz[fa[f][0]]-=k[i];
}
}
dfs1(1,0);
for(int i=1; i<=n; i++)
if(del[i]==0)
printf("%d %lld\n",i,ans[i]);
return 0;
}