描述
给定一棵N个节点的有根树,树上每个节点有一个非负整数权重Wi。定义节点集合S={i1, i2, ……, ir}的总权重为:(⊕是异或运算)
每次询问一棵子树内所有可能出现的总权重数量,即令E为所询问的子树内节点的集合,
|T|即为可能出现的总权重数量。
输入
第一行包含两个整数N,Q,表示树的节点数目和询问数目,节点1总是这棵树的根部。
第二行包含N-1个整数,第i个整数P_i表示i+1号节点的父亲节点。数据保证Pi≤i。
第三行包含N个整数,表示每个节点的权重Wi。
第四行包含Q个整数,每个整数Qi表示询问子树Qi内的可能出现的总权重数量
N≤100000,Q≤100000,Wi≤260
输出
输出共Q行,每行包含一个整数T表示子树Qi内可能出现的总权重数量
7 3 1 2 2 1 5 5 8 4 3 1 2 4 6 2 5 1样例输出
8 4 16
题意:给出一棵树,每次询问给你一个节点x,让你求以x为根的子树集合的所有子集异或和有多少种。
思路:这很明显就是一个线性基的题目,dfs求出以每个节点为子树的线性基的大小cnt,然后2的cnt次幂就是答案。线性基可以合并,从叶子节点开始创建线性基,父亲节点的线性基就为各个子节点的线性基的合并再插入自己的权值。
线性基的讲解:点击打开链接
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
struct LineBasis
{
LL b[66];
LL p[66];
int cnt;
LineBasis()
{
memset(b,0,sizeof(b));
memset(p,0,sizeof(p));
cnt = 0;
}
void Insert(LL val)
{
for(int i=60;i>=0;i--)
{
if((1LL<<i)&val)
{
if(b[i] == 0)
{
b[i] = val;
break;
}
val ^= b[i];
}
}
if(val > 0)
cnt++;
}
LineBasis Merge(LineBasis n1,LineBasis n2)
{
LineBasis ret = n1;
for(int i=0;i<=60;i++)
if(n2.b[i])
ret.Insert(n2.b[i]);
return ret;
}
};
LineBasis num[100010];
vector<int> v[100010];
LL a[100010];
void dfs(int x)
{
for(int i=0;i<v[x].size();i++)
{
int xx = v[x][i];
dfs(xx);
num[x] = num[x].Merge(num[x],num[xx]);
}
num[x].Insert(a[x]);
}
int main(void)
{
int n,q,i,j;
while(scanf("%d%d",&n,&q)==2)
{
for(i=1;i<=n;i++)
v[i].clear();
for(i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
v[x].push_back(i);
}
for(i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
dfs(1);
while(q--)
{
int x;
scanf("%d",&x);
int cnt = num[x].cnt;
LL ans = 1;
for(i=1;i<=cnt;i++)
ans *= 2;
printf("%lld\n",ans);
}
}
return 0;
}