2020牛客暑期多校训练营A National Pandemic(树链剖分)

本文介绍了一种模拟国家疫情严重性的方法,该问题基于一棵树的数据结构。利用树链剖分来处理疫情蔓延、消除和查询操作。分析了不同操作对城市疫情严重性的影响,并给出了算法实现思路,包括lca(最近公共祖先)计算和线段树的维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A National Pandemic

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

1
5 6
1 2
1 3
2 4
2 5
1 1 5
3 4
2 1
1 2 7
3 3
3 1

输出

3
9
6

题目大意

有一个国家,它的城市分布可以表现为一棵树。其中爆发了疫情,我们定义 f ( n ) f(n) f(n)表示 n n n号城市的疫情严重性。有以下3种操作:
1、蔓延,给定一个 x , w x,w x,w。对于所有城市 y y y,其严重程度会加上 w − d i s t ( x , y ) w-dist(x,y) wdist(x,y),其中 d i s t ( x , y ) dist(x,y) dist(x,y)表示两个城市之间的距离。可以为负数。
2、消除,给定一个 y y y,将 y y y城市的 f ( y ) f(y) f(y)更改为 m i n ( 0 , f ( y ) ) min(0,f(y)) min(0,f(y))
3、询问,给定一个 x x x,输出 f ( x ) f(x) f(x)
如果出现负数,可以忽略。现对于每个询问,要求输出答案。

分析

分析算法,显然,在一棵树上进行加减询问的,就是树剖

那么我们对于每个操作进行分析。

  • o p 1 op1 op1
    发现有 d i s t dist dist这个函数,我们想在树上求距离,是用 l c a lca lca做的。不妨我们将每个城市的变换展开:
    x , y , l c a x,y,lca x,y,lca的深度为 d e p [ x ] , d e p [ y ] , d e p [ l c a ] dep[x],dep[y],dep[lca] dep[x],dep[y],dep[lca]
    f ( y ) + = w − d i s t ( x , y ) f(y)+=w-dist(x,y) f(y)+=wdist(x,y)
           = w − ( d e p [ x ] + d e p [ y ] − 2 d e p [ l c a ] ) \qquad\,\,\,\,\,\,=w-(dep[x]+dep[y]-2dep[lca]) =w(dep[x]+dep[y]2dep[lca])
           = w − d e p [ x ] − d e p [ y ] + 2 d e p [ l c a ] \qquad\,\,\,\,\,\,=w-dep[x]-dep[y]+2dep[lca] =wdep[x]dep[y]+2dep[lca]
    我们可以发现,对于一个 o p 1 op1 op1而言, w − d e p [ x ] w-dep[x] wdep[x]是固定的,而对于每个城市 y y y而言, d e p [ y ] dep[y] dep[y]也是固定的。因此不妨我们开一个变量存储:
    A + = w − d e p [ x ] , B + + ; A+=w-dep[x],\qquad B++; A+=wdep[x],B++;
    A A A 存所有关于 x x x的效果, B B B d e p [ y ] dep[y] dep[y]的次数。
    这样对于每次询问或者消除,我都可以通过查询之前存下的所有蔓延的累加效果。所以,如果要用 f ( y ) f(y) f(y)时,我们可以这样算:
    f ( y ) = A − B ∗ d e p [ y ] + 2 ∑ ( d e p [ l c a ] ) f(y)=A-B*dep[y]+2\sum(dep[lca]) f(y)=ABdep[y]+2(dep[lca])
  • o p 2 op2 op2
    对于这个操作,由于我们要考虑是正还是负,所以对于 m i n ( 0 , f ( y ) ) min(0,f(y)) min(0,f(y))我们也存储一下。
    不妨搞一个 C C C数组,存入每次 y y y的消除结果。因此有:
    C y + = m i n ( 0 , f ( y ) ) − f ( y ) C_y+=min(0,f(y))-f(y) Cy+=min(0,f(y))f(y)
    之后无论是算答案,还是继续,只要在上面那串后面加上 C y C_y Cy即可。即:
    f ( y ) = A − B ∗ d e p [ y ] + 2 ∑ ( d e p [ l c a ] ) + C y f(y)=A-B*dep[y]+2\sum(dep[lca])+C_y f(y)=ABdep[y]+2(dep[lca])+Cy
  • o p 3 op3 op3
    这个经过上面的简化之后,它就简单了。如上,我已经写出了求解的公式。

那现在有个问题吼,怎么搞 l c a lca lca了?不妨画个图试试。
在这里插入图片描述
图中红色点为 x x x,其他节点中的数字为和 x x x l c a lca lca的深度。
绿色路径是红色节点到根的路径,那么可以发现,每个节点与 x x x l c a lca lca的深度是这个节点到根的路径上的绿色边的数量。那么我们只要搞下树剖,然后线段树维护一下就可以了,见代码。

代码

#include<bits/stdc++.h>
#define debug cerr<<"debug";
#define ll long long
#define ls i<<1
#define rs i<<1|1
using namespace std;
const int MAXN=1e5+10;
int siz[MAXN],ldfn[MAXN],rdfn[MAXN],dep[MAXN],tot,A,B;
int son[MAXN],fa[MAXN],id[MAXN],top[MAXN],val[MAXN],C[MAXN];
vector<int> vec[MAXN];
void predfs(int pos)
{
	siz[pos]=1;son[pos]=0;
	for(int i=0;i<vec[pos].size();i++){
		int s=vec[pos][i];
		if(s==fa[pos]) continue;
		fa[s]=pos;dep[s]=dep[pos]+1;
		predfs(s);siz[pos]+=siz[s];
		if(siz[s]>siz[son[pos]]) son[pos]=s;
	}
}//树剖板子
void dfs(int pos,int tp)
{
	top[pos]=tp;ldfn[pos]=++tot;id[tot]=pos;
	if(son[pos]) dfs(son[pos],tp);
	for(int i=0;i<vec[pos].size();i++){
		int s=vec[pos][i];
		if(s==son[pos]||s==fa[pos]) continue;
		dfs(s,s);
	}rdfn[pos]=tot;
}//树剖板子++
struct tree{
	int l,r;
	ll inc,sum;
}tr[MAXN<<2];
void build(int i,int l,int r)
{
	tr[i].l=l;
	tr[i].r=r;
	tr[i].inc=0;
	if(l==r){
		tr[i].sum=0;
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	tr[i].sum=tr[i<<1].sum+tr[i<<1|1].sum;
}
void add(int i,int a,int b,int c)
{
	if(tr[i].l==a&&tr[i].r==b){
		tr[i].inc+=c;
		return;
	}
	tr[i].sum+=(b-a+1)*c;
	int mid=(tr[i].l+tr[i].r)>>1;
	if(b<=mid) add(i<<1,a,b,c);
	else if(a>mid) add(i<<1|1,a,b,c);
	else{
		add(i<<1,a,mid,c);
		add(i<<1|1,mid+1,b,c);
	}
}
ll query(int i,int a,int b)
{
	if(tr[i].l==a&&tr[i].r==b)
		return tr[i].sum+tr[i].inc*(b-a+1);
	if(tr[i].inc){
		tr[i].sum+=tr[i].inc*(tr[i].r-tr[i].l+1);
		tr[i<<1].inc+=tr[i].inc;
		tr[i<<1|1].inc+=tr[i].inc;
		tr[i].inc=0;
	}
	int mid=(tr[i].l+tr[i].r)>>1;
	if(b<=mid) return query(i<<1,a,b);
	else if(a>mid) return query(i<<1|1,a,b);
	else return query(i<<1,a,mid)+query(i<<1|1,mid+1,b);
}//线段树区间加减板子
void CP(int x,int y,ll val)//CP change path
{
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]) swap(x,y);
		add(1,ldfn[top[y]],ldfn[y],val);y=fa[top[y]];
	}if(dep[x]>dep[y]) swap(x,y);
	if(x==y) return;//这里是边转点,因此lca不能算
	add(1,ldfn[x]+1,ldfn[y],val);
}//更改绿色边
int GPS(int x,int y)//GPS get path sum
{
	ll ret=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]) swap(x,y);
		ret+=query(1,ldfn[top[y]],ldfn[y]);y=fa[top[y]];
	}if(dep[x]>dep[y]) swap(x,y);
	if(x==y) return ret;//同
	ret+=query(1,ldfn[x]+1,ldfn[y]);
	return ret;
}//查询路径上的绿色边
int main()
{
	int t,n,m,p,x,y,k,op;scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=0;i<=n;i++) vec[i].clear();
		for(int i=1;i<n;i++){
			scanf("%d%d",&x,&y);
			vec[x].push_back(y);
			vec[y].push_back(x);
		}tot=0;dep[1]=0;
		predfs(1);dfs(1,1);
		build(1,1,tot);A=B=0;
		memset(C,0,sizeof(C));
		while(m--){
			scanf("%d",&op);
			if(op==1){
				scanf("%d%d",&x,&k);
				A+=k-dep[x];B++;
				CP(1,x,2ll);//这里将上面的式子中的*2放到每个式子里做了
			}
			if(op==2){
				scanf("%d",&x);
				ll ans=0;
				ans+=GPS(1,x);
				ans=A-B*dep[x]+ans+C[x];
				C[x]+=min(0ll,ans)-ans;
			}
			if(op==3){
				scanf("%d",&x);
				ll ans=0;ans+=GPS(1,x);
				ans=A-B*dep[x]+ans+C[x];
				printf("%lld\n",ans);
			}
		}
	}
}

END

不会吧不会吧,不会还有人没有习惯阴间函数名吧,不会吧不会吧……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值