hdu5909 (点分治+dfs序上树形DP

hdu5909 (点分治+dfs序上树形DP

题意:

问你全部点权异或为0~k的子树的数量

思路:

连通块问题,显然统计要包含所有的连通块,连通块问题可以考虑点分,点分过程中以分治重心为根即可以不重不漏包含所有连通块。

所以我们暴力固定分治重心为根即连通块中必选点,考虑一个点选了,其儿子才能选,否则其儿子不能选,这种连通块依赖问题,可以用dfs序转为树上dp

对每个分治区域做dfs序,

d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前连通块dfs序上前i个点决策完,异或和为j的方案数,

下一个点要么选

-> d p [ i + 1 ] [ j 异 或 a [ d f n [ i + 1 ] ] dp[i+1][j异或a[dfn[i+1]] dp[i+1][ja[dfn[i+1]]

要么不选

-> d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j],暴力 d p dp dp统计方案数即可

时间复杂度 O ( n m l o g n ) O(nmlogn) O(nmlogn)

这种点分中必选分治重心从而统计所有连通块状态的套路值得记录

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int maxn=1005;
int dp[1005][1026],head[maxn],ver[maxn<<1],next1[maxn<<1],tot,ans[1026],a[maxn],sz1[maxn],sz2[maxn],mxson[maxn],S,rt,ti=0,dfn[maxn],m,n;
bool v[maxn];
void add_edge(int x,int y){
    ver[++tot]=y,next1[tot]=head[x],head[x]=tot;
}
void add(int&x,int y){
    x+=y;
    if(x>=mod)x-=mod;
}
void init1(){
    tot=0;
    for(int i=1;i<=n;++i)head[i]=0;
    for(int i=0;i<m;++i)ans[i]=0;
}
void getRoot(int x,int f){
    sz1[x]=1;mxson[x]=0;
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(y==f||v[y])continue;
        getRoot(y,x);
        sz1[x]+=sz1[y];
        mxson[x]=max(mxson[x],sz1[y]);
    }
    mxson[x]=max(mxson[x],S-mxson[x]);
    if(mxson[x]<mxson[rt])rt=x;
}
void init(int x){
    S=sz1[x];
    mxson[rt=0]=maxn;
    getRoot(x,0);
}
void dfs(int x,int f){
    sz2[x]=1;dfn[++ti]=x;
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(y==f||v[y])continue;
        dfs(y,x);
        sz2[x]+=sz2[y];
    }
}
void cal(int x){//重心必选
    ti=0;
    dfs(x,0);
    for(int i=1;i<=ti;++i)
        for(int j=0;j<m;++j)dp[i][j]=0;
    dp[1][a[x]]=1;
    for(int i=1;i<ti;++i)
        for(int j=0;j<m;++j){
            add(dp[i+1][j^a[dfn[i+1]]],dp[i][j]);
            add(dp[i+sz2[dfn[i+1]]][j],dp[i][j]);
        }
    for(int i=0;i<m;++i)add(ans[i],dp[ti][i]);
}
void dfz(int x){
    v[x]=1;
    cal(x);
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(v[y])continue;
        init(y);
        dfz(rt);
    }
    v[x]=0;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i=1;i<=n;++i)cin>>a[i];
        init1();
        int x,y;
        for(int i=1;i<n;++i){
            cin>>x>>y;
            add_edge(x,y);add_edge(y,x);
        }
        mxson[rt=0]=maxn;
        S=n;
        getRoot(1,0);
        dfz(rt);
        for(int i=0;i<m-1;++i){
            cout<<ans[i]<<" ";
        }
        cout<<ans[m-1]<<"\n";
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值