题意:
给出一棵树,每个节点有个val,q个询问。
询问格式:u, v, k, 从u到v的路径, 每个k个点取一个,求所有点的val值异或和。
如果没有隔k个取1个的限制的话,那么做法是很显然的,dfs一遍,求出所有点到根的异或和,ans=xor(u, root)^xor(v, root)^(lca(u, v),root)。
现在有步长的限制,取m=sqrt(n),
1)预处理出步长k<=m时每个点到根的异或和。
2)当询问k>m时,暴力求取。
虽然k>m时是暴力求取,但这建立在我们能够快速求得anc[u][k] (u向上k级的祖先)的基础上。为了快速求取anc[u][k],可以用倍增或者树链剖分实现。
以下的AC代码基于树链剖分实现,
代码含注释。
#include
#include
#include
#include
#include
#include
#define debug puts("Infinity is awesome!")
#define mm(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
const int maxn=5e4+50;
const LL mod=1e9+7;
const double eps=1e-8;
struct Edge{
int to, next;
};
Edge edges[2*maxn];
int n, m, q;
int head[maxn], ecnt;
int depth[maxn], anc[maxn][233];
int cnt;
int siz[maxn], son[maxn], top[maxn], tid[maxn], rak[maxn];
int val[maxn], jut[maxn][233];
void Init(){
ecnt=0; cnt=0;
mm(head, -1);
mm(jut, 0);
}
void AddEdge(int u,int v){
edges[ecnt]=Edge{v, head[u]};
head[u]=ecnt++;
}
void Build(int u,int f,int d){
int maxc=0, maxb=0;
siz[u]=1; depth[u]=d;
anc[u][1]=f;
for(int i=head[u];i!=-1;i=edges[i].next){
int v=edges[i].to;
if(v==f) continue;
Build(v, u, d+1);
siz[u]+=siz[v];//更新子树大小
if(siz[v]>maxc){//找出每个节点的最大子儿子
maxc=siz[v];
maxb=v;
}
}
son[u]=maxb;//记录最大子儿子
}
void Dfs(int u,int tp){//树链剖分
top[u]=tp; tid[u]=++cnt;//记录重链的最高点,剖分后的ID
rak[cnt]=u;//ID对应的节点
for(int i=1;i<=m;i++)//从u跳i步,直到根节点的异或和
jut[u][i]=jut[anc[u][i]][i]^val[u];
if(son[u]) Dfs(son[u], tp);//重边向下构造重链
for(int i=head[u];i!=-1;i=edges[i].next){
int v=edges[i].to;
if(v==anc[u][1]||v==son[u]) continue;
Dfs(v, v);//轻边向下构造轻链,最高点为v
}
}
int Lca(int x,int y){//根据重链求lca
while(top[x]!=top[y]){
if(depth[top[x]]1];
}
if(depth[x]return y;
}
int Jump(int x,int k){//从x向上跳k步,返回x的k级祖先
if(depth[x]<=k) return 0;
int y=top[x];
while(x&&k>=depth[x]-depth[y]+1){
k-=depth[x]-depth[y]+1;
x=anc[y][1], y=top[x];
}
if(x==0) return 0;
return rak[tid[x]-k];
}
int Get(int x,int y,int k,int s){//返回x到y,步长为k的异或和
int ret=0; //s取值0或1,lca的val只能被异或一次,需要异或lca时s=0
if(depth[x]return 0;
if(k<=m){
ret^=jut[x][k];
while(depth[x]>=depth[y]+s){
x=Jump(x, k);
}
ret^=jut[x][k];
return ret;
}
ret^=val[x];
while(depth[x]>=depth[y]+k+s){
x=Jump(x, k);
ret^=val[x];
}
return ret;
}
int main(){
int T;
int cas=0;
int u, v, k;
//scanf("%d", &T);
//while(T--){
while(~scanf("%d%d", &n, &q)){
m=(int)sqrt(n+0.5);
Init();
for(int i=1;iscanf("%d%d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
}
for(int i=1;i<=n;i++){
scanf("%d", &val[i]);
}
Build(1, 0, 1);
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
anc[i][j]=anc[anc[i][j-1]][1];
Dfs(1, 1);
for(int i=0;iscanf("%d%d%d", &u, &v, &k);
int z=Lca(u, v);
int dis=depth[u]+depth[v]-2*depth[z];
int ans=0;
ans^=Get(u, z, k, 0); //printf("ans=%d\n", ans);
if(dis%k) v=Jump(v, dis%k); //printf("v=%d\n", v);
ans^=Get(v, z, k, 1);
printf("%d\n", ans);
}
}
return 0;
}
原题(BZOJ4381)题解链接。
区别在于求和,求异或和。