hdu5909:Tree Cutting (FWT/点分治优化树形DP)

27 篇文章 0 订阅
7 篇文章 0 订阅

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5909


题目大意:给出一棵树,每个节点的权值都在 [0,m) [ 0 , m ) 内。现在对于每个 v[0,m) v ∈ [ 0 , m ) ,你都应给出权值为 v v 的至少有一个点的子连通块个数。一个连通块的权值定义为其所有点的权值异或和。答案模109+7,多组数据。


题目分析:首先很容易想到 O(Tnm2) O ( T n m 2 ) 的树形DP。由于树的一个子连通块也是一棵树,可以使1号点为整棵树的根,定义子连通块的根为其中深度最小的节点。然后令f[node][v]表示以node为根,异或和为v的子连通块有多少个。显然,当逐个考虑node的儿子的时候,考虑新的儿子son对f[node][v]的贡献,发现有以下形式的转移:

f[node][v]=t=0m1f[node][t]f[son][vt] f [ n o d e ] [ v ] = ∑ t = 0 m − 1 f [ n o d e ] [ t ] ∗ f [ s o n ] [ v ⊕ t ]

其中 是异或操作。

这个方程显然可以用FWT优化,这样 O(Tnmlog(m)) O ( T n m log ⁡ ( m ) ) 就可以通过所有数据。然而这样常数很大,方法也不够巧妙,有没有别的优化方法呢?

我们发现本题是询问关于树上的连通块问题,因为不知道连通块的根,所以要对每个节点DP。假设强制这个连通块过根,能不能降低DP的时间复杂度呢?我们又发现:如果这个连通块过根,那么若一个点不选,它的子树也不能选;否则它的子树才有可能选。如果定义一个指针指向当前考虑的节点的话,那么前者就表示这个指针移向了DFS序上该子树对应区间的右一位,而后者就代表指针直接移向DFS序的下一位。这样按DFS序去DP,时间就是 O(nm) O ( n m ) 的。

那么如何处理不过根的问题呢?只要套个点分治就行了。这样总时间复杂度是 O(Tnmlog(n)) O ( T n m log ⁡ ( n ) ) 的,不仅方法优美而且十分好写。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1050;
const long long M=1000000007;
typedef long long LL;

struct edge
{
    int obj;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur;

int tree[maxn];
int num;

int Size[maxn];
int max_Size[maxn];

int dfsx[maxn];
int st[maxn];
int ed[maxn];

LL f[maxn][maxn];
LL ans[maxn];

int val[maxn];
bool vis[maxn];

int t,n,m;

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

void Dfs1(int node,int fa)
{
    num++;
    tree[num]=node;
    Size[node]=1;
    max_Size[node]=0;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if ( son==fa || vis[son] ) continue;
        Dfs1(son,node);
        Size[node]+=Size[son];
        max_Size[node]=max(max_Size[node],Size[son]);
    }
}

void Dfs2(int node,int fa)
{
    num++;
    dfsx[num]=node;
    st[node]=num;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if ( son==fa || vis[son] ) continue;
        Dfs2(son,node);
    }
    ed[node]=num;
}

void Solve(int node)
{
    num=0;
    Dfs1(node,node);
    if (num==1)
    {
        ans[ val[node] ]=(ans[ val[node] ]+1)%M;
        return;
    }

    int root=tree[1];
    for (int i=1; i<=num; i++)
    {
        int x=tree[i];
        Size[x]=max(Size[node]-Size[x],max_Size[x]);
        if (Size[x]<Size[root]) root=x;
    }

    num=0;
    Dfs2(root,root);
    for (int i=2; i<=num+1; i++)
        for (int j=0; j<m; j++) f[i][j]=0;

    f[2][ val[ dfsx[1] ] ]=1;
    for (int i=2; i<=num; i++)
        for (int j=0; j<m; j++)
        {
            LL &x=f[i+1][ j^val[ dfsx[i] ] ];
            x=(x+f[i][j])%M;
            LL &y=f[ ed[ dfsx[i] ]+1 ][j];
            y=(y+f[i][j])%M;
        }
    for (int i=0; i<m; i++) ans[i]=(ans[i]+f[num+1][i])%M;

    vis[root]=true;
    for (edge *p=head[root]; p; p=p->Next)
    {
        int son=p->obj;
        if (vis[son]) continue;
        Solve(son);
    }
}

int main()
{
    freopen("5909.in","r",stdin);
    freopen("5909.out","w",stdout);

    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&n,&m);
        for (int i=1; i<=n; i++)
            head[i]=NULL,vis[i]=false,scanf("%d",&val[i]);
        cur=-1;
        for (int i=1; i<n; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            Add(x,y);
            Add(y,x);
        }

        for (int i=0; i<m; i++) ans[i]=0;
        Solve(1);
        for (int i=0; i<m-1; i++) printf("%d ",ans[i]);
        printf("%d\n",ans[m-1]);
    }

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值