3853 -- 三只企鹅
题目大意:
- 给定一棵 n 个点的树,然后有 m 个操作,分为修改操作和查询操作。
- 修改操作:金企鹅向点 u 空投零食,其他每个点产生快递费,快递费等于 u 到该点的距离。
- 查询操作:金企鹅想询问点 u 至今为止产生的快递费。
------------
题解:对于修改操作,每次选一个点,所有点的权值加上到该点的距离,如果暴力修改显然超时。
我们可以先考虑这样一个问题: 选择出点u,那么一个指定点v产生的贡献是多少?答案是 : $dep[u]+dep[v]-2*dep[lca(u,v)]$
这是一对点,如果多次修改呢?
显然我们可以把上面式子拆成两个部分设某次查询操作之前选了这些点空头:{ $s_1$ , $s_2$ , $s_3$ ,....., $s_n$ }
那么 $dep[u]$ 就是: $\sum_{i=1}^ndep[s_i]$ $\ $ 这个可以记下来。
$\qquad $ $dep[v]$ 就是:$n*dep[v]$ $\ $ 这个可以查询的时候直接计算
那么剩下我们就只需要统计: $ 2*\sum_{i=1}^ndep[lca(s_i,v)] $ 就是所有的 $lca$ 的深度和。
你发现一个点的深度其实是这个点到根节点上所有的边权和,两个点的 $lca$ 的深度就是重合部分,我们考虑把边权下放到点上,那选出点时就可以把这个点到根节点上所有的点都加上相对应的边权,查询的时候就是求出这个点到根节点上所有的权值和。问题就转化成了树上一条链上区间加和区间求和,很自然的想到**树链剖分**。
最后线段树你可能会对状态的设置产生一些疑问,可以设 $s$ 表示总和, $sum$ 表示对应的点权(边权)和, $lazy$ 表示这个区间加了多少次,每次 $pushdown$ 的时候就 $s+=sum*lazy$ 表示直接加上这个区间。本题就做完了。附代码。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct stu{
int y,z;
};
vector<stu>p[200005];
long long dd[200005];
int dep[200005];
int siz[200005],son[200005],tp[200005],fa[200005],id[200005],rk[200005],tot=0;
int qz[200005];
void dfs1(int i,int ff){
fa[i]=ff;
dep[i]=dep[ff]+1;
siz[i]=1;
for(int j=0;j<p[i].size();j++){
int to=p[i][j].y,val=p[i][j].z;
if(to==ff)continue;
qz[to]=val;
dd[to]=dd[i]+val;
dfs1(to,i);
siz[i]=siz[i]+siz[to];
if(siz[to]>siz[son[i]])son[i]=to;
}
}
void dfs2(int i,int top){
tp[i]=top;
id[i]=++tot;
rk[tot]=i;
if(son[i])dfs2(son[i],top);
for(int j=0;j<p[i].size();j++){
int to=p[i][j].y;
if(to==son[i]||to==fa[i])continue;
dfs2(to,to);
}
}
long long ans1=0,gs=0,ans2=0;
long long sum[200005*4],lazy[200005*4],tree[200005*4];
void build(int k,int l,int r){
if(l==r){
sum[k]=qz[rk[l]];
return;
}
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
sum[k]=sum[k*2]+sum[k*2+1];
}
void pushdown(int k){
if(lazy[k]){
lazy[k*2]+=lazy[k];
lazy[k*2+1]+=lazy[k];
tree[k*2]+=sum[k*2]*lazy[k];
tree[k*2+1]+=sum[k*2+1]*lazy[k];
lazy[k]=0;
}
}
void xg(int k,int l,int r,int L,int R,int val){
if(l>R||r<L)return;
if(l>=L&&r<=R){
lazy[k]++;
tree[k]+=sum[k];
return;
}
pushdown(k);
int mid=(l+r)/2;
xg(k*2,l,mid,L,R,val);
xg(k*2+1,mid+1,r,L,R,val);
tree[k]=tree[k*2]+tree[k*2+1];
}
long long query(int k,int l,int r,int L,int R){
if(l>R||r<L)return 0;
if(l>=L&&r<=R)return tree[k];
pushdown(k);
int mid=(l+r)/2;
long long res=0;
res=res+query(k*2,l,mid,L,R);
res=res+query(k*2+1,mid+1,r,L,R);
return res;
}
void change(int x,int y,int u){
while(tp[x]!=tp[y]){
if(dep[tp[x]]<dep[tp[y]])swap(x,y);
xg(1,1,n,id[tp[x]],id[x],dd[u]);
x=fa[tp[x]];
}
if(id[x]>id[y])swap(x,y);
xg(1,1,n,id[x],id[y],dd[u]);
}
long long qquery(int x,int y){
long long res=0;
while(tp[x]!=tp[y]){
if(dep[tp[x]]<dep[tp[y]])swap(x,y);
res=res+query(1,1,n,id[tp[x]],id[x]);
x=fa[tp[x]];
}
if(id[x]>id[y])swap(x,y);
res=res+query(1,1,n,id[x],id[y]);
return res;
}
int main(){
// freopen("4.in","r",stdin);
// freopen("4.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int n1,n2,n3;
scanf("%d%d%d",&n1,&n2,&n3);
p[n1].push_back((stu){n2,n3});
p[n2].push_back((stu){n1,n3});
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
int opt,u;
scanf("%d%d",&opt,&u);
if(opt==1){
gs++;
ans1=ans1+dd[u];
change(u,1,u);
}else {
ans2=ans1+gs*dd[u];
ans2=ans2-2*qquery(u,1);
printf("%lld\n",ans2);
}
}
return 0;
}
本题和1988染色本质一样,只是1988一个点只能贡献一次,所以需要开个数组记一下防止加重。(~~双倍经验~~)

浙公网安备 33010602011771号