牛客多校7 - A National Pandemic(树链剖分+线段树)

题目链接:点击查看

题目大意:给出一棵树,再给出 m 次操作,每次操作分为三种类型,dist( x , y ) 代表点 x 和点 y 之间的距离:

  1. 1 pos val:将点 pos 位置的值增加 val ,将其余所有点 x 的值,增加 val - dist( pos , x )
  2. 2 pos:点 pos 位置的值与 0 取 min
  3. 3 pos:查询点 pos 位置的值

题目分析:参考博客:https://blog.csdn.net/tianyizhicheng/article/details/107734665

第二个操作显然是充数的,额外用一个 delta 数组随便维护一下就好了,主要是操作 1 和操作 3

对于每次操作 1 来说,肯定不能暴力去维护所有的 n 个点,所以我们不妨将点权转换为每个点与 root 节点( 设为点 1 )的相对权值

牛客多校7 - A National Pandemic(树链剖分+线段树)_第1张图片

画个图然后分类讨论一下吧,现在假设我们将点 6 的权值增加 w,那么显然根节点(点1)的权值会增加 w - 2 ,我们将点 1 的权值记为 all ,此时也就是 all = w - 2,因为点 6 的深度为 2

如果我们想要求与点 6 的 lca 为 root 的点的权值,也就是点 1 , 2 , 3 , 4 的权值,显然 all - deep[ x ] 就是答案了,因为 lca 为 1 ,所以这些点与点 1 的距离增大,相应的与点 6 的距离也会增大,答案自然也会变小

既然我们想让答案与 deep 形成关系,对于那些,与点 6 的 lca 不为 root 的点,如:点 5 , 6 , 7 ,也需要构造一个公式,也就是 all - deep[ x ] + y 为点 x 的权值,通过观察不难发现,这个 y 值可以分两种情况讨论:

  1. 当点 x 位于 点 1 ~ 点 6 的路径上,即点 5 和 6 ,那么 y 的值为点 1 ~ 点 x 的边数 * 2
  2. 否则,y 的值为点 1 ~ 点 6 的边数 * 2

综上所述,对于操作 1 来说,需要执行的操作就是:

  1. all += w - deep[ x ]
  2. 将点 1 ~ 点 x 这条路径上的边权 + 2

对于操作 3 查询来说,答案就是 all + ( 点 1 ~ 点 x 这条边上的边权之和 ) - deep[ x ] * num

注意,这里的 deep 为什么突然乘以 num 了,解释一下这个突然出现的 num ,其意义是操作 1 的数量,举个很简单的例子,还是上图,如果对点 6 进行两次操作 1 ,增加的权值都是 w ,那么此时的 all = ( w -  2 ) * 2 ,如果要求点 2 的权值,答案应该是 all - deep[ 2 ] * 2 而不是 all - deep[ 2 ]

剩下的就是实现了,对于树上路径的区间更改和区间查询,可以利用树链剖分和线段树来执行,因为是要对边权操作,所以可以将边权转换为点权,很基本的操作,直接实现就好了

操作 2 的 delta 就不多说了,如果操作 1 明白了的话,操作 2 看一眼代码应该就会了

代码:
 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

typedef long long LL;

typedef unsigned long long ull;

const int inf=0x3f3f3f3f;

const int N=5e4+100;

LL all,tot,delta[N];

vectornode[N];

int deep[N],fa[N],son[N],num[N];
 
void dfs1(int u,int f,int dep)
{
    deep[u]=dep;
    son[u]=-1;
    num[u]=1;
    fa[u]=f;
    for(auto v:node[u])
    {
        if(v==f)
            continue;
        dfs1(v,u,dep+1);
        num[u]+=num[v];
        if(son[u]==-1||num[son[u]]>1;
    }
    LL cal_len()
    {
        return r-l+1;
    }
}tree[N<<2];
 
void pushup(int k)
{
    tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
}
 
void pushdown(int k)
{
    LL lz=tree[k].lazy;
    tree[k<<1].sum+=tree[k<<1].cal_len()*lz;
    tree[k<<1|1].sum+=tree[k<<1|1].cal_len()*lz;
    tree[k<<1].lazy+=lz;
    tree[k<<1|1].lazy+=lz;
    tree[k].lazy=0;
}
 
void build(int k,int l,int r)
{
    tree[k].l=l;
    tree[k].r=r;
    tree[k].sum=tree[k].lazy=0;
    if(l==r)
        return;
    int mid=tree[k].mid();
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
}
 
void update(int k,int l,int r)
{
	if(l>r)
		return;
    if(tree[k].rr)
        return;
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].lazy+=2;
        tree[k].sum+=tree[k].cal_len()*2;
        return;
    }
    if(tree[k].lazy)
        pushdown(k);
    update(k<<1,l,r);
    update(k<<1|1,l,r);
    pushup(k);
}
 
LL query(int k,int l,int r)
{
	if(l>r)
		return 0;
	if(tree[k].rr)
        return 0;
    if(tree[k].l>=l&&tree[k].r<=r)
        return tree[k].sum;
    if(tree[k].lazy)
        pushdown(k);
    return query(k<<1,l,r)+query(k<<1|1,l,r);
}

void change(int x)
{
	while(top[x]!=1)
	{
		update(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	update(1,id[1]+1,id[x]);
}

LL ask(int x)
{
	LL ans=0;
	while(top[x]!=1)
	{
		ans+=query(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	ans+=query(1,id[1]+1,id[x]);
	return ans;
}

void init(int n)
{
	for(int i=0;i<=n;i++)
	{
		delta[i]=0;
		node[i].clear();
	}
	cnt=tot=all=0;
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int w;
	cin>>w;
	while(w--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		init(n);
		for(int i=1;idelta[x])
					delta[x]=temp;
			}
			else if(op==3)
			{
				int x;
				scanf("%d",&x);
				printf("%lld\n",all+ask(x)-deep[x]*tot-delta[x]);
			}
		}
	}















    return 0;
}

 

你可能感兴趣的:(树链剖分,线段树)