hihocoder 1723 : 子树统计 (线性基)

描述

给定一棵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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值