2020牛客暑期多校训练营(第七场)C. A National Pandemic
https://ac.nowcoder.com/acm/contest/5672/C
题意
给出一颗n个点的树,初始点权为0,执行m次操作
1 x w:给点x的点权加w,其余所有点点权加w-dis(i,x)
2 x:将点x的点权和0取min
3 x:查询x的点权
解法一:树链剖分+线段树
对于操作1来说,必须要寻找一种方式,将对所有点的修改进行统一
树上两点间距离很容易想到dis(x,i)=dep(x)+dep(i)-dep(lca)*2,dep表示深度
则w-dis(x,i)=w-dep(x)-dep(i)+dep(lca)*2
其中
w-dep(x)对于所有的点都是一样的,用一个变量记录一下即可
dep(i)是不会改变的,用一个变量记录操作1的次数即可
只有dep(lca)比较难处理
假设对如图所示进行操作1 10 5
红色的数表示每个点实际要加的值
蓝色的数表示dep(lca)
差分的思想,将每个点与它的父节点做差
那就只有从根节点到10号点的路径上,每个点要进行1的修改
dep(lca)*2,就是每个点要进行2的修改
相当于在根节点到10号点的路径上加一个公差为2的等差数列
具体查询某个点的dep(lca)时,查询根节点到这个点的路径上所有点的和即可
即进行的修改相当于差分,然后前缀和求值
给树上的某条路径上所有点加一个值,以及求树上路径点权和,这两种操作树链剖分之后线段树即可
对于操作2,查询出该点的点权之后,如果点权>0,记下差值即可
对于操作3,用a表示 Σ(w-dep(x)),b表示操作1的次数,del表示操作2的差值,s表示查询结果
答案就是a-b*dep+s-del
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define N 50001 typedef long long LL; int n; int front[N],to[N<<1],nxt[N<<1],tot; int fa[N],dep[N],siz[N]; int id[N],bl[N],dy[N]; LL del[N]; LL sum[N<<2],flag[N<<2]; LL s; void add(int u,int v) { to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; } void dfs1(int x) { siz[x]=1; for(int i=front[x];i;i=nxt[i]) if(to[i]!=fa[x]) { fa[to[i]]=x; dep[to[i]]=dep[x]+1; dfs1(to[i]); siz[x]+=siz[to[i]]; } } void dfs2(int x,int top) { id[x]=++tot; dy[tot]=x; bl[x]=top; int m=0; for(int i=front[x];i;i=nxt[i]) if(to[i]!=fa[x] && siz[to[i]]>siz[m]) m=to[i]; if(!m) return; dfs2(m,top); for(int i=front[x];i;i=nxt[i]) if(to[i]!=fa[x] && to[i]!=m) dfs2(to[i],to[i]); } void down(int k,int l,int mid,int r) { sum[k<<1]+=(mid-l+1)*flag[k]; sum[k<<1|1]+=(r-mid)*flag[k]; flag[k<<1]+=flag[k]; flag[k<<1|1]+=flag[k]; flag[k]=0; } void change(int k,int l,int r,int opl,int opr) { if(l>=opl && r<=opr) { sum[k]+=r-l+1<<1; flag[k]+=2; return; } int mid=l+r>>1; if(flag[k]) down(k,l,mid,r); if(opl<=mid) change(k<<1,l,mid,opl,opr); if(opr>mid) change(k<<1|1,mid+1,r,opl,opr); sum[k]=sum[k<<1]+sum[k<<1|1]; } void Change(int u) { int v=1; while(bl[u]!=bl[v]) { if(dep[bl[u]]<dep[bl[v]]) swap(u,v); change(1,1,n,id[bl[u]],id[u]); u=fa[bl[u]]; } if(dep[u]>dep[v]) swap(u,v); change(1,1,n,id[u],id[v]); } void query(int k,int l,int r,int opl,int opr) { if(l>=opl && r<=opr) { s+=sum[k]; return; } int mid=l+r>>1; if(flag[k]) down(k,l,mid,r); if(opl<=mid) query(k<<1,l,mid,opl,opr); if(opr>mid) query(k<<1|1,mid+1,r,opl,opr); } void Query(int u) { int v=1; s=0; while(bl[u]!=bl[v]) { if(dep[bl[u]]<dep[bl[v]]) swap(u,v); query(1,1,n,id[bl[u]],id[u]); u=fa[bl[u]]; } if(dep[u]>dep[v]) swap(u,v); query(1,1,n,id[u],id[v]); } int main() { /* int size = 256 << 20; // 256MB char *p = (char*)malloc(size) + size; __asm__("movl %0, %%esp\n" :: "r"(p)); */ // freopen("data.txt","r",stdin); // freopen("my.txt","w",stdout); int T,m,u,v,op; LL a,tmp; int b; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); tot=0; memset(front,0,sizeof(front)); for(int i=1;i<n;++i) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dep[1]=1; dfs1(1); tot=0; dfs2(1,0); a=b=0; memset(sum,0,sizeof(sum)); memset(flag,0,sizeof(flag)); memset(del,0,sizeof(del)); while(m--) { scanf("%d",&op); if(op==1) { scanf("%d%d",&u,&v); a+=v-dep[u]; b++; Change(u); } else { scanf("%d",&u); Query(u); tmp=a-1ll*b*dep[u]+s-del[u]; if(op==2) { if(tmp>0) del[u]+=tmp; } else cout<<tmp<<'\n'; } } } }
解法二:点分树
太晚了,明天再补