[Contest20171102]简单数据结构题

给一棵$n$个点的数,点权开始为$0$,有$q$次操作,每次操作选择一个点,把周围一圈点点权$+1$,在该操作后你需要输出当前周围一圈点点权的异或和。

由于输出量较大,设第$i$个询问输出为$ans_i$,你只需要输出$\sum\limits_{i=1}^qans_i(i^2+i)\mod (10^9+7)$

 

异或和的话,用trie存比较方便

具体点:每个节点建一棵trie,存它的所有儿子的权值,trie上的tag维护数字数量和当前子树内的异或和

①对所有儿子$+1$

其实就是把一棵trie中的所有数字$+1$

为了模拟加法,我们的trie是从低位开始建的

当前位可能是$0$或$1$,如果是$0$,$+1$后会变成$1$,如果是$1$,$+1$后会变成$0$并进位

那么我们直接交换当前trie节点的左右儿子,并递归进入$0$儿子

修改过程中顺便维护异或和

②对父亲$+1$

为了知道一个节点当前的值,我们需要两个标记

$d_{x}$表示节点$x$的增值

$ds_x$表示节点$x$的所有儿子的增值

那么一个节点当前的值就是$d_x+ds_{fa_x}$

所以我们对$x$的父亲$+1$时,只需要在$fa_{fa_x}$的trie中删除$d_{fa_x}$,让$d_x+1$,然后再插入回去

于是就愉快地解决啦

#include<stdio.h>
#define ll long long
struct edge{
	int to,nex;
}e[1000010];
int h[500010],d[500010],sd[500010],fa[500010],root[500010],ch[20000010][2],siz[20000010],xorsum[20000010],tot;
void add(int a,int b){
	tot++;
	e[tot].to=b;
	e[tot].nex=h[a];
	h[a]=tot;
}
void pushup(int x){
	xorsum[x]=((xorsum[ch[x][0]]^xorsum[ch[x][1]])<<1)|(siz[ch[x][1]]&1);
}
void insert(int&x,int v,int p){
	if(x==0){
		tot++;
		x=tot;
	}
	siz[x]++;
	if(p==19)return;
	insert(ch[x][(v>>p)&1],v,p+1);
	pushup(x);
}
void dfs(int x){
	for(int i=h[x];i;i=e[i].nex){
		if(e[i].to!=fa[x]){
			fa[e[i].to]=x;
			dfs(e[i].to);
			insert(root[x],0,0);
		}
	}
}
void swap(int&a,int&b){a^=b^=a^=b;}
void plus(int x,int p){
	if(x==0||p==19)return;
	swap(ch[x][0],ch[x][1]);
	plus(ch[x][0],p+1);
	pushup(x);
}
void erase(int&x,int v,int p){
	siz[x]--;
	if(p==19){
		if(siz[x]==0)x=0;
		return;
	}
	erase(ch[x][(v>>p)&1],v,p+1);
	if(siz[x]==0)
		x=0;
	else
		pushup(x);
}
ll query(int x){
	sd[x]++;
	plus(root[x],0);
	if(x==1)return xorsum[root[x]];
	if(fa[x]==1){
		d[1]++;
		return xorsum[root[x]]^d[1];
	}
	erase(root[fa[fa[x]]],d[fa[x]]+sd[fa[fa[x]]],0);
	d[fa[x]]++;
	insert(root[fa[fa[x]]],d[fa[x]]+sd[fa[fa[x]]],0);
	return xorsum[root[x]]^(d[fa[x]]+sd[fa[fa[x]]]);
}
int main(){
	int n,q,i,a,b;
	ll ans=0;
	scanf("%d%d",&n,&q);
	for(i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	tot=0;
	dfs(1);
	for(i=1;i<=q;i++){
		scanf("%d",&a);
		ans=(ans+query(a)*(i*(ll)i+(ll)i))%1000000007;
	}
	printf("%lld",ans);
}

转载于:https://www.cnblogs.com/jefflyy/p/7774668.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值