[bzoj4835]遗忘之树

57 篇文章 0 订阅
40 篇文章 0 订阅

题目描述

定义任意两点之间存在唯一路径的无向图是树。对于一棵n个点的树,如果删掉某个点u之后每个连通块的大小均不
超过n/2,那么称u为这棵树的重心。现在有一棵n个点的树T,利用过程P来构造一个n个点的有向图G,初始G没有边
。现在对T调用过程P,P的内容如下:
1:删去u,对每个连通块递归调用过程P;
2:对每个连通块,如果它的标号最小的重心为v,那么在图G中连一条u到v的有向边。
3:现在小Q同学手里有一个图G,但是不记得原来T的样子了,希望你能通过G来恢复T,但是可能得到的T会有很多种
你只需要告诉小Q同学可能的T的个数。
两棵树被认为是不同的,当且仅当存在一对点(u,v),使得u和v在一棵树中有边,在另一棵树中没有边。

DP

设f[i]表示以点分树中i为根有多少种原树。
对于i的儿子j,如果size[j]不是size[i]的一半,直接f[i]*=f[j]*size[j]。
否则,按照题目的定义,只能选取编号大的,我们找到j子树中所有编号大于i的,设个数为k,则f[i]*=f[j]*k。
可以用线段树合并,也可以暴力。暴力的复杂度是对的是因为显然最坏情况是满二叉树,此时复杂度是n log n。

#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=100000+10,maxtot=2000000+10,mo=1000000007;
ll f[maxn];
int tree[maxtot],left[maxtot],right[maxtot],root[maxn];
int size[maxn],h[maxn],go[maxn],next[maxn],d[maxn];
int i,j,k,l,t,n,m,tot,ca,ans,wdc;
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
int newnode(int x){
    ++tot;
    tree[tot]=tree[x];
    left[tot]=left[x];
    right[tot]=right[x];
    return tot;
}
void insert(int &x,int l,int r,int a){
    x=newnode(x);
    if (l==r){
        tree[x]++;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) insert(left[x],l,mid,a);else insert(right[x],mid+1,r,a);
    tree[x]=tree[left[x]]+tree[right[x]];
}
int query(int x,int l,int r,int a,int b){
    if (a>b) return 0;
    if (!x) return 0;
    if (l==a&&r==b) return tree[x];
    int mid=(l+r)/2;
    if (b<=mid) return query(left[x],l,mid,a,b);
    else if (a>mid) return query(right[x],mid+1,r,a,b);
    else return query(left[x],l,mid,a,mid)+query(right[x],mid+1,r,mid+1,b);
}
int merge(int a,int b,int l,int r){
    if (!a||!b) return a+b;
    if (l==r){
        tree[a]+=tree[b];
        return a;
    }
    int mid=(l+r)/2;
    left[a]=merge(left[a],left[b],l,mid);
    right[a]=merge(right[a],right[b],mid+1,r);
    tree[a]=tree[left[a]]+tree[right[a]];
    return a;
}
void dfs(int x){
    int t=h[x],k;
    size[x]=1;
    f[x]=1;
    while (t){
        dfs(go[t]);
        size[x]+=size[go[t]];
        t=next[t];
    }
    t=h[x];
    while (t){
        if (size[x]%2==0&&size[go[t]]==size[x]/2) k=query(root[go[t]],1,n,x+1,n);
        else k=size[go[t]];
        f[x]=(ll)f[x]*f[go[t]]%mo*k%mo;
        root[x]=merge(root[x],root[go[t]],1,n);
        t=next[t];
    }
    insert(root[x],1,n,x);
}
int main(){
    //freopen("data.in","r",stdin);
    scanf("%d",&ca);
    while (ca--){
        scanf("%d%d",&n,&m);
        fo(i,1,n) h[i]=d[i]=root[i]=0;
        tot=0;
        fo(i,1,m){
            scanf("%d%d",&j,&k);
            d[k]++;
            add(j,k);
        }
        fo(i,1,n)
            if (!d[i]){
                wdc=i;
                break;
            }
        tot=0;
        dfs(wdc);
        ans=f[wdc];
        (ans+=mo)%=mo;
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值