jzoj 5908. 【NOIP2018模拟10.16】开荒(树链剖分)

4 篇文章 0 订阅
2 篇文章 0 订阅

Description

题目背景:

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

题目描述:

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


Input

第一行 n ,q;
接下来 1 行 n 个数,代表每个人的分值;
接下来 n-1 行 u,v 代表一条边
接下来 q 行
Q 代表询问,接下来 k 个数代表组织的人数,读入为 0时停止读入。
C 代表修改,输入 x,w 代表将 x 的分值变为 w


Output

共 Q 的数量行,为开荒的人的总分值


Sample Input

4 4
10 5 2 2
1 2
2 3
2 4
Q 3 4 0
C 3 200
Q 3 4 0
Q 1 4 0


Sample Output

9
207
17
样例解释:
第一次询问,参加的人有 2,3,4 5+2+2=9
第一次修改,权值为 10 5 200 2
第二次询问,参加的人有 2,3,4 5+200+2=207
第三次询问,参加的人有 1,2,4 10+5+2=17


Data Constraint

20%的数据 n<=10000,q<=500;
另外 20%的数据 k=2
另外 20%的数据 没有修改操作
所有数据 n,q<=100000,所有询问 k 的和<=1000000
保证数据合法


分析:

裸的树剖
难点在于我们如何快速的处理询问。在询问时我们将询问的点按照树剖的dfs序排序,每次讲相邻两点(第一个点和最后一个点也算相邻的两点)的路径上的和加入ans。每条路径都会被统计两次,ans/2即为最后答案。但相邻两点的lca会被统计不止两次,我们可以将这些lca单独拿出来,最后再加入答案。


code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long 
#define lowbit(x) (x&-x)
using namespace std;
const LL N=1e5+10;
LL n,m,b[N],last[N],size[N],v[N],deep[N],top[N],fa[N],f[N*4],tr1[N*4],tag[N*4],ans,T,Z,zl[N];
bool bz[N];
struct node{
	LL a,b;
	node(){}
	node(LL x,LL y) {a=y,b=last[x];}
}a[N*2],c[N];
void add(LL x,LL y) {
	a[++a[0].a]=node(x,y);last[x]=a[0].a;
}
void change(LL x,LL y) {
	LL i=x;
	while (i<=n) {
		f[i]+=y;
		i+=lowbit(i);
	}
}
LL sum(LL x) {
	LL y=0;
	while (x>0) y+=f[x],x-=lowbit(x);
	return y; 
}
void dfs1(LL x,LL y) {
	LL i=last[x];
	fa[x]=y;
	for (LL i=last[x];i;i=a[i].b) {
		if (a[i].a!=y) {
			deep[a[i].a]=deep[x]+1;
			dfs1(a[i].a,x);
			size[x]+=size[a[i].a];
		}
	}
	size[x]++;
}
void dfs2(LL x,LL y) {
	LL i,z=0;
	v[x]=++T;
	for (i=last[x];i;i=a[i].b) {
		if (a[i].a!=y) {
			if (size[z]<=size[a[i].a]) z=a[i].a;
		}
	}
	if (z!=0) {
		top[z]=top[x];
		dfs2(z,x);
	}
	for (i=last[x];i;i=a[i].b) {
		if (a[i].a!=y && a[i].a!=z) {
			top[a[i].a]=a[i].a;
			dfs2(a[i].a,x);
		}
	}
}
LL lca(LL x,LL y) {
	LL z1=x,z2=y;
	while (top[x]!=top[y]) {
		if (deep[top[x]]<deep[top[y]]) swap(x,y);
		ans+=sum(v[x])-sum(v[top[x]]-1);
		x=fa[top[x]];
	}
	if (deep[x]<deep[y]) swap(x,y);
	ans+=sum(v[x])-sum(v[y]);
	return y;

}
LL lca1(LL x,LL y) {
	LL z1=x,z2=y;
	while (top[x]!=top[y]) {
		if (deep[top[x]]<deep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if (deep[x]<deep[y]) swap(x,y);
	if (!bz[y]) {
		Z+=b[y],bz[y]=true,zl[++zl[0]]=y;
		change(v[y],-b[y]);
	}
	return y;

}
bool cmp(node a,node b) {
	return a.b<b.b;
}
int main() {
	// freopen("kaihuang.in","r",stdin);
	// freopen("kaihuang.out","w",stdout);
	LL i,j,k;
	memset(tag,-1,sizeof(tag));
	scanf("%lld%lld",&n,&m);
	for (i=1;i<=n;i++) scanf("%lld",&b[i]);
	for (i=1;i<=n-1;i++) {
		LL x,y;scanf("%lld%lld",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(1,0);
	top[1]=1;
	dfs2(1,0);
	for (i=1;i<=n;i++) change(v[i],b[i]);
	for (i=1;i<=m;i++) {
		scanf("\n");
		char ch=getchar();
		if (ch=='Q') {
			LL x;scanf("%lld",&x);
			c[0].a=0;
			while (x) c[++c[0].a].a=x,c[c[0].a].b=v[x],scanf("%lld",&x);
			if (c[0].a==1) {
				printf("%lld\n",b[c[1].a]);
				continue;
			}
			sort(c+1,c+c[0].a+1,cmp);
			c[++c[0].a].a=c[1].a;
			ans=Z=zl[0]=0;
			for (j=1;j<=c[0].a-1;j++) lca1(c[j].a,c[j+1].a);
			for (j=1;j<=c[0].a-1;j++) lca(c[j].a,c[j+1].a);
			printf("%lld\n",(ans+Z*2)/2);
			for (j=1;j<=zl[0];j++) {
				bz[zl[j]]=false;
				change(v[zl[j]],b[zl[j]]);
			}
		}
		else {
			LL x,y;scanf("%lld%lld",&x,&y);
			change(v[x],y-b[x]);
			b[x]=y;
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值