P3401 洛谷树 题解
隐形霉菌死于此树下。
解法
考虑树链剖分,首先先将父亲与儿子间的边权转化为儿子的点权。
根据异或的性质,a⊕a=0a \oplus a = 0a⊕a=0,我们记每个节点到根的边权异或和为 valival_ivali,任意两点间的边权异或和为 disi,jdis_{i,j}disi,j,这样,我们可以将树上任意两点 x,yx,yx,y 间边权的异或和表示为 valx⊕valyval_x \oplus val_yvalx⊕valy。考虑如下证明: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}valx⊕valy=valx⊕vallca⊕vallca⊕valy=disx,lca⊕dislca,y=disx,y,其中 lcalcalca 表示 xxx 和 yyy 的最近公共祖先。
原问题被转化为 xxx 到 yyy 间路径上的任意两点间的异或和的和。
注意到交换求和顺序是不影响答案的,并且此题值域很小,所以我们将题目从每个点对的每一个二进制位的贡献转化为了每一个二进制位上每个点对的贡献。
考虑异或的性质,只有 0⊕1=10 \oplus 1 = 10⊕1=1,即一个点对的某个二进制位不同时这个点对在这个二进制位上才有贡献,我们可以使用线段树维护每一个二进制位上有 cnt0cnt0cnt0 个 000 和 cnt1cnt1cnt1 个 111,根据简单的组合,这个二进制位有 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 yvalx⊕wx⊕y,异或 wxw_xwx 的目的是撤销曾经 wxw_xwx 对 valxval_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;
}
洛谷树题解:利用树链剖分和线段树解决异或值路径和问题,
135





