bzoj4835 树重心分解引发的计数问题!4月份月赛

Description

定义任意两点之间存在唯一路径的无向图是树。对于一棵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在一棵树中有边,在另一棵树中没有边。

Input

 第一行是一个整数T(1≤T≤1000),表示测试数据的组数。

对于每组测试数据:
第一行是两个整数n和m(2≤n,m≤100000),表示G的点数的边数。
接下来m行,每行是两个整数u和v(1≤u,v≤n),表示有一条从u到v的有向边。
保证对于每组测试数据,至少存在一棵树T,使得对T调用过程P之后可以得到G
并且所有测试数据的n之和、m之和均不超过10^6。

Output

对于每组测试数据,输出一行一个非负整数,表示这组数据的答案对(10^9+7)取模的值。


题解:既然每次取u都是树重心,这无非就是一个分治的过程,不断进行树中心分解。这样计数的时候,就是各个子树可以构造的个数的乘积!

统计子树个数时。因为题目中有要求对每个连通块,如果它的标号最小的重心为v,那么在图G中连一条u到v的有向边。什么时候会出现多个重心!--树的节点数为偶数个!所以只要树的节点数为奇数的时候,可以随便排!但是如果为偶数的时候,就找u的子树中节点值大于u的个数,同上进行乘法运算即可!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
struct node{int b,next;}edge[100010];
int cnt=0,n,m,vis[100010],head[100010],Size[100010];ll dp[100010];
void add(int a,int b){edge[cnt].b=b;edge[cnt].next=head[a];head[a]=cnt++;}
int Find(int u,int p)
{
    int ans=u>p;
    for(int i=head[u];i!=-1;i=edge[i].next)ans+=Find(edge[i].b,p);
    return ans;
}
void dfs(int u)
{
    Size[u]=1,dp[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        node e=edge[i];dfs(e.b);
        Size[u]=(Size[u]+Size[e.b])%mod;
    }
    int half=Size[u]%2==0?Size[u]/2:0;//n%2==0的时候有两个重心,需要讨论,否则不需要讨论。
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        node e=edge[i];int v=e.b;
        if(Size[v]==half) dp[u]=dp[u]*dp[v]%mod*Find(v,u)%mod;
        else dp[u]=dp[u]*dp[v]%mod*Size[v]%mod;
    }
}
int main()
{
    int t,a,b;scanf("%d",&t);
    while(t--)
    {
        memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));scanf("%d%d",&n,&m);cnt=0;
        while(m--){scanf("%d%d",&a,&b);add(a,b);vis[b]=1;}
        for(int i=1;i<=n;i++)if(!vis[i]){dfs(i);printf("%lld\n",dp[i]);break;}
    }return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值