P3401 洛谷树 题解

洛谷树题解:利用树链剖分和线段树解决异或值路径和问题,

P3401 洛谷树 题解

隐形霉菌死于此树下。

解法

考虑树链剖分,首先先将父亲与儿子间的边权转化为儿子的点权。

根据异或的性质,a⊕a=0a \oplus a = 0aa=0,我们记每个节点到根的边权异或和为 valival_ivali,任意两点间的边权异或和为 disi,jdis_{i,j}disi,j,这样,我们可以将树上任意两点 x,yx,yx,y 间边权的异或和表示为 valx⊕valyval_x \oplus val_yvalxvaly。考虑如下证明:valx⊕valy=valx⊕vallca⊕vallca⊕valy=disx,lca⊕dislca,y=disx,yval_x \oplus val_y = val_x \oplus val_{lca} \oplus val_{lca} \oplus val_y = dis_{x,lca} \oplus dis_{lca,y} = dis_{x,y}valxvaly=valxvallcavallcavaly=disx,lcadislca,y=disx,y,其中 lcalcalca 表示 xxxyyy 的最近公共祖先。

原问题被转化为 xxxyyy 间路径上的任意两点间的异或和的和。

注意到交换求和顺序是不影响答案的,并且此题值域很小,所以我们将题目从每个点对的每一个二进制位的贡献转化为了每一个二进制位上每个点对的贡献。

考虑异或的性质,只有 0⊕1=10 \oplus 1 = 101=1,即一个点对的某个二进制位不同时这个点对在这个二进制位上才有贡献,我们可以使用线段树维护每一个二进制位上有 cnt0cnt0cnt0000cnt1cnt1cnt1111,根据简单的组合,这个二进制位有 cnt0×cnt1cnt0 \times cnt1cnt0×cnt1 次贡献,对和的贡献为 cnt0×cnt1×2icnt0 \times cnt1 \times 2^icnt0×cnt1×2i

线段树维护 valvalval 的值即可。

至于修改操作,我们设节点 xxx 原来的点权(我们已经将边权转化为了点权)为 wxw_xwx,要修改成的值为 yyy,那新的 valxval_xvalx 即为 valx⊕wx⊕yval_x \oplus w_x \oplus yvalxwxy,异或 wxw_xwx 的目的是撤销曾经 wxw_xwxvalxval_xvalx 的贡献。注意到这个修改会影响到节点 xxx 的子树的 valvalval 值,所以,我们对其子树进行同样的操作即可。

操作一为树链剖分,线段树区间查询,操作二为字数操作,线段树区间修改。

代码

#include<bits/stdc++.h>
namespace fast_IO
{
	/**
	 * 屎山快读快写
	*/
};
using namespace fast_IO;
const int N=30010;
int n,m,dep[N],fa[N],siz[N],hvson[N],id[N],times,start[N],w[N],neww[N],v[N],val[10];
struct edge
{
	int to,cost;
	edge *next;
};
edge *head[N];
inline void add(const int x,const int y,const int z)
{
	edge *e=new edge;
	e->to=y,e->next=head[x],e->cost=z,head[x]=e;
}
inline void dfs1(int pos,int f,int depth,int maxi=0)
{
	fa[pos]=f,dep[pos]=depth,siz[pos]=1;
	for(edge *i=head[pos];i!=nullptr;i=i->next)
		if(i->to!=f)
		{
			w[i->to]=w[pos]^i->cost,v[i->to]=i->cost,dfs1(i->to,pos,depth+1),siz[pos]+=siz[i->to];
			if(siz[i->to]>maxi) maxi=siz[i->to],hvson[pos]=i->to;
		}
}
inline void dfs2(int pos,int Start)
{
	start[pos]=Start,id[pos]=++times,neww[times]=w[pos];
	if(hvson[pos]) dfs2(hvson[pos],Start);
	for(edge *i=head[pos];i!=nullptr;i=i->next)
		if(i->to!=fa[pos] && i->to!=hvson[pos]) dfs2(i->to,i->to);
}
struct node
{
	int cnt0[10],cnt1[10],lazy[10];
	node *lc,*rc;
	inline node()
	{
		memset(cnt0,0,sizeof(cnt0)),memset(cnt1,0,sizeof(cnt1)),memset(lazy,0,sizeof(lazy));
		lc=rc=nullptr;
	}
	inline void pushup()
	{
		for(int i=0;i<10;i++) cnt0[i]=lc->cnt0[i]+rc->cnt0[i],cnt1[i]=lc->cnt1[i]+rc->cnt1[i];
	}
	inline void pushdown()
	{
		for(int i=0;i<10;i++)
			if(lazy[i])
			{
				std::swap(lc->cnt0[i],lc->cnt1[i]),std::swap(rc->cnt0[i],rc->cnt1[i]);
				lc->lazy[i]^=1,rc->lazy[i]^=1,lazy[i]=0;
			}
	}
};
class seg_tree
{
	#define ls l,mid
	#define rs mid+1,r
private:
	node *root;
	inline node *build(int l,int r)
	{
		node *rt=new node();
		if(l==r) for(int i=0;i<10;i++) rt->cnt1[i]=((neww[l]>>i)&1),rt->cnt0[i]=rt->cnt1[i]^1;
		else
		{
			int mid=(l+r)/2;
			rt->lc=build(ls),rt->rc=build(rs),rt->pushup();
		}
		return rt;
	}
	inline void fix(node *rt,const int L,const int R,int l,int r)
	{
		if(L<=l && r<=R)
		{
			for(int i=0;i<10;i++)
				if(val[i])
					std::swap(rt->cnt0[i],rt->cnt1[i]),rt->lazy[i]^=1;
			return;
		}
		int mid=(l+r)/2;
		rt->pushdown();
		if(L<=mid) fix(rt->lc,L,R,ls);
		if(R>mid) fix(rt->rc,L,R,rs);
		rt->pushup();
	}
	inline node ask(node *rt,const int L,const int R,int l,int r)
	{
		if(L<=l && r<=R) return *rt;
		int mid=(l+r)/2;
		rt->pushdown();
		if(L>mid) return ask(rt->rc,L,R,rs);
		if(R<=mid) return ask(rt->lc,L,R,ls);
		node ll=ask(rt->lc,L,R,ls),rr=ask(rt->rc,L,R,rs),ret;
		ret.lc=&ll,ret.rc=&rr,ret.pushup();
		return ret;
	}
public:
	inline void build()
	{
		root=build(1,n);
	}
	inline void fix(const int L,const int R)
	{
		fix(root,L,R,1,n);
	}
	inline node ask(const int L,const int R)
	{
		return ask(root,L,R,1,n);
	}
};
seg_tree tree;
inline long long ask(int x,int y)
{
	long long ret=0;
	long long cnt0[10],cnt1[10];
	memset(cnt0,0,sizeof(cnt0)),memset(cnt1,0,sizeof(cnt1));
	node tmp;
	while(start[x]!=start[y])
	{
		if(dep[start[x]]<dep[start[y]]) std::swap(x,y);
		tmp=tree.ask(id[start[x]],id[x]),x=fa[start[x]];
		for(int i=0;i<10;i++) cnt0[i]+=tmp.cnt0[i],cnt1[i]+=tmp.cnt1[i];
	}
	if(dep[x]>dep[y]) std::swap(x,y);
	tmp=tree.ask(id[x],id[y]);
	for(int i=0;i<10;i++) cnt0[i]+=tmp.cnt0[i],cnt1[i]+=tmp.cnt1[i];
	for(int i=0;i<10;i++) ret+=cnt0[i]*cnt1[i]*(1ll<<i);
	return ret;
}
int main()
{
	in>>n>>m;
	for(int i=1,x,y,z;i<n;i++) in>>x>>y>>z,add(x,y,z),add(y,x,z);
	dfs1(1,0,1),dfs2(1,1),tree.build();
	for(int i=1,op,x,y,z;i<=m;i++)
	{
		in>>op>>x>>y;
		if(op==1) out<<ask(x,y)<<'\n';
		else if(op==2)
		{
			in>>z;
			if(dep[x]<dep[y]) std::swap(x,y);
			memset(val,0,sizeof(val));
			for(int j=0;j<10;j++) if(((z>>j)&1)!=((v[x]>>j)&1)) val[j]=1;
			tree.fix(id[x],id[x]+siz[x]-1),v[x]=z;
		}
	}
	fwrite(Ouf,1,p3-Ouf,stdout),fflush(stdout);
	return 0;
}
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值