JZOJ5908. 【NOIP2018模拟10.16】开荒(kaihuang)

30 篇文章 0 订阅
4 篇文章 0 订阅

Description

题目背景:
尊者神高达作为一个萌新,在升级路上死亡无数次后被一只大黄叽带回了师门。他加入师门后发现有无穷无尽的师兄弟姐妹,这几天新副本开了,尊者神高达的师门作为一个 pve师门,于是他们决定组织一起去开荒。

题目描述:
师门可以看做以 1 为根的一棵树,师门中的每一个人都有一定的装备分数。一共会有 q 个事件。每个事件可能是一次开荒,也可能是因为开荒出了好装备而导致一个人的装分出现了变化。对于一次开荒,会有 k 个人组织,由于师门的号召力很强,所以所有在组织者中任意两个人简单路径上的人都会参加。

题解

可以想到是用虚树来实现,
那么现在问题就转化成了求树上某些链的权值和,
而且还有支持单点修改。
这就是链剖了。
其实虚树也不一定要真正地建出来,
只是用类似的方法求出那一棵树。

code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#define N 200003
#define P putchar
#define G getchar
#define ls (x<<1)
#define rs (x<<1|1)
#define LL long long
using namespace std;
char ch;
void read(int &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    int w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int abs(int x){return x<0?-x:x;}
int sqr(int x){return x*x;}
void write(LL x){if(x>9) write(x/10);P(x%10+'0');}

LL l[N*4],r[N*4],V[N*4],ops,ans,sum;
int nxt[N],to[N],lst[N],v[N],vv[N],tot;
int si[N],top[N],son[N],fa[N],id[N],now,rank[N];
int dep[N],f[17][N];
int n,m,t,opl,opr,opx,x,y,q,a[N];
int z[N*2],tp;

void ins(int x,int y)
{
    nxt[++tot]=lst[x];
    to[tot]=y;
    lst[x]=tot;
}

void dfs(int x)
{
	rank[x]=++now;dep[x]=dep[fa[x]]+1;f[0][x]=fa[x];
    int t=0;si[x]=1;
    for(int i=lst[x];i;i=nxt[i])
        if(to[i]!=fa[x])
        {
            fa[to[i]]=x;
            dfs(to[i]);
            si[x]+=si[to[i]];
            if(si[to[i]]>t)t=si[to[i]],son[x]=to[i];
        }
}

void dfs_(int x,int t)
{
    vv[id[x]=++now]=v[x];
    top[x]=t;
    if(son[x])dfs_(son[x],t);
    for(int i=lst[x];i;i=nxt[i])
        if(to[i]!=fa[x] && to[i]!=son[x])
            dfs_(to[i],to[i]);
}

void build(int x,int ll,int rr)
{
    l[x]=ll;r[x]=rr;
    if(ll==rr)
    {
        V[x]=vv[ll];
        return;
    }
    int m=(ll+rr)>>1;
    build(ls,ll,m);
    build(rs,m+1,rr);
    V[x]=V[ls]+V[rs];
}

void work(int x)
{
    if(opl<=l[x] && r[x]<=opr)
    {
        if(opx==1)V[x]=ops;else
        if(opx==2)ops=ops+V[x];
        return;
    }
    int m=(l[x]+r[x])>>1;
    if(opl<=m)work(ls);
    if(m<opr)work(rs);
    V[x]=V[ls]+V[rs];
}

void find(int x,int y)
{
    for(;dep[y]<dep[top[x]];x=fa[top[x]])
        opl=id[top[x]],opr=id[x],work(1);
    opl=id[y];opr=id[x];work(1);
}

int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int j=16;j+1;j--)
		if(dep[f[j][x]]>=dep[y])x=f[j][x];
	if(x==y)return x;
	for(int j=16;j+1;j--)
		if(f[j][x]^f[j][y])x=f[j][x],y=f[j][y];
	return f[0][x];
}

bool cmp(int x,int y){return rank[x]<rank[y];}

int main()
{
    freopen("kaihuang.in","r",stdin);
    freopen("kaihuang.out","w",stdout);

	read(n);read(m);
	for(int i=1;i<=n;i++)read(v[i]);
	for(int i=1;i<n;i++)read(x),read(y),ins(x,y),ins(y,x);

    now=0,dfs(1);
    now=0,dfs_(1,1);
    build(1,1,n);
    for(int j=1;j<17;j++)
    	for(int i=1;i<=n;i++)
    		f[j][i]=f[j-1][f[j-1][i]];

	for(int i=1;i<=m;i++)
	{
		for(;ch!='Q' && ch!='C';ch=G());
		if(ch=='Q')
		{
			for(a[q=0]=-1;a[q];)read(a[++q]);q--;
			sort(a+1,a+1+q,cmp);ops=v[a[1]];opx=2;
			z[tp=1]=a[1];
			for(int i=2;i<=q;i++)
			{
				x=lca(a[i],z[tp]),find(a[i],x),ops=ops-v[x];
				for(;tp && dep[z[tp]]>dep[x];tp--);
				if(tp==0)find(z[1],x),ops=ops-v[z[1]];
				z[++tp]=x;z[++tp]=a[i];
			}
			write(ops);P('\n');
		}
		else
		{
			read(x);read(y);opx=1;
			opl=opr=id[x];ops=y;work(1);
			v[x]=y;
		}
	}

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值