ZJOI2017 仙人掌

28 篇文章 0 订阅

题解

如果一开始的图就不是仙人掌,答案显然为0,可以Tarjan判断。
环显然不能产生贡献,所以可以把环边都断开。
现在模型转化为,给定一棵树,用路径去覆盖树上的每一条边,且路径不能相交,求方案数。
fi 表示做完了 i 的子树,且没有路径可以向上扩展。
gi表示做完了 i 的子树,且有路径可以向上扩展。
hi表示有 i 个点,它们之间匹配的方案数。
num为点 x 的儿子个数,那么显然

hi=hi1+hi2×(i1)

fx=Πgson×hnum

gx=fx+Πgson×hnum1×num

简单解释一下:
hi 转移的时候考虑当前第 i 个儿子的选择,如果这个儿子不匹配,那就有hi1种方案,如果匹配,那就可以和前面 i1 个儿子中的一个匹配,方案是 (i1)×hi2
fx 的转移:每个儿子都必须要可以往上扩,且各个儿子之间相对独立所以是 Πgson ,然后一共有 hnum 种儿子的匹配方案,所以乘起来就是所有可能的方案。
gx 的转移:首先 x 自己可以往上扩展,方案就是fx,然后 x 还可以选择一个儿子,记这个儿子为y,匹配方案为 gy ,那么剩下的儿子有 Πson!=y gson×hnum1 种方案,乘起来就是 Πgson×hnum1 由于 y 的取值有num种选择所以还要乘上 num

时间复杂度: O(n)

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 500000 + 10
#define M 1000000 + 10
typedef long long ll ;
const int MO = 998244353 ;

bool is_cactus ;
bool vis[N] , flag[2*M] ;
int Node[2*M] , From[2*M] , Next[2*M] , Head[N] , tot ;
int S[N] , DFN[N] , LOW[N] , Col[N] , Tim , top ;
int f[N] , g[N] , h[N] ;
int Case , n , m ;
ll ans ;

int Read() {
    int ret = 0 ;
    char ch = getchar() ;
    while ( ch < '0' || ch > '9' ) ch = getchar() ;
    while ( ch >= '0' && ch <= '9' ) {
        ret = ret * 10 + ch - '0' ;
        ch = getchar() ;
    }
    return ret ;
}

void Pre() {
    h[0] = 1 ;
    for (int i = 1 ; i <= 500000 ; i ++ ) {
        h[i] = h[i-1] + ( i > 1 ? 1ll * h[i-2] * (i - 1) % MO : 0 ) ;
        if ( h[i] >= MO ) h[i] %= MO ;
    }
}

void link( int u , int v ) {
    Node[++tot] = v ;
    From[tot] = u ;
    Next[tot] = Head[u] ;
    Head[u] = tot ;
    flag[tot] = 1 ;
}

void Tarjan( int x , int F ) {
    DFN[x] = LOW[x] = ++ Tim ;
    S[++top] = x ;
    bool flag = 0 ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == F ) continue ;
        if ( !DFN[Node[p]] ) {
            Tarjan( Node[p] , x ) ;
            LOW[x] = min( LOW[x] , LOW[Node[p]] ) ;
            if ( LOW[Node[p]] < DFN[x] ) {
                if ( flag ) { is_cactus = 0 ; return ; }
                flag = 1 ;
            }
        } else {
            LOW[x] = min( LOW[x] , DFN[Node[p]] ) ;
            if ( DFN[Node[p]] < DFN[x] ) {
                if ( flag ) { is_cactus = 0 ; return ; }
                flag = 1 ;
            }
        }
    }
    if ( DFN[x] == LOW[x] ) {
        while ( 1 ) {
            Col[S[top]] = x ;
            top -- ;
            if ( S[top+1] == x ) break ;
        }
    }
}

void DFS( int x , int F ) {
    vis[x] = 1 ;
    f[x] = 1 , g[x] = 0 ;
    int num = 0 ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( !flag[p] || Node[p] == F ) continue ;
        DFS( Node[p] , x ) ;
        f[x] = 1ll * f[x] * g[Node[p]] % MO ;
        num ++ ;
    }
    g[x] = (1ll * f[x] * h[num] % MO + 1ll * f[x] * h[num-1] % MO * num % MO) % MO ;
    f[x] = 1ll * f[x] * h[num] % MO ;
}

int main() {
    freopen( "cactus.in" , "r" , stdin ) ;
    freopen( "cactus.out" , "w" , stdout ) ;
    Pre() ;
    Case = Read() ;
    while ( Case -- ) {
        is_cactus = 1 , ans = 1 ;
        tot = Tim = top = 0 ;
        n = Read() , m = Read() ;
        for (int i = 1 ; i <= n ; i ++ ) {
            vis[i] = 0 ;
            Head[i] = Col[i] = DFN[i] = LOW[i] = 0 ;
        }
        for (int i = 1 ; i <= m ; i ++ ) {
            int u = Read() , v = Read() ;
            link( u , v ) ;
            link( v , u ) ;
        }
        Tarjan( 1 , 0 ) ;
        if ( !is_cactus ) { printf( "0\n" ) ; continue ; }
        for (int i = 1 ; i <= tot ; i ++ ) if ( Col[From[i]] == Col[Node[i]] ) flag[i] = 0 ;
        for (int i = 1 ; i <= n ; i ++ ) {
            if ( vis[i] ) continue ;
            DFS( i , 0 ) ;
            ans = ans * f[i] ;
            if ( ans >= MO ) ans %= MO ;
        }
        printf( "%lld\n" , ans ) ;
    }
    return 0 ;
}

以上.

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值