JZOJ4949. 仙人球

题目大意

给定一个 n 个点的仙人球。
现在要从图中选出不超过k个点,满足这 k 个点直接联通。求方案数。

Data Constraint
n1000,k500

题解

将仙人掌进行缩环之后,显然能得到一棵树。
然后就可以树形DP了。设 fi,j 表示第 i 个环的子树中,环i顶点必选,选了 j 个点的方案数。
至于处理环,可以先将一条边断开,然后做一个后缀和前缀合并。

时间复杂度:O(nk2)

SRC

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

#define K 100 + 10
#define N 5000 + 10
#define M 20000000 + 10
typedef long long ll ;
const int MO = 1e9 + 7 ;

vector < int > G[N] ;

bool vis[N] ;
int DFN[N] , LOW[N] , S[N] , Tim ;
ll f[N][K] , f1[N][K] , f2[N][K] , g[N][K] , h[N][K] ;
int Cir[N][2*N] , Size[N] , Belong[N] , from[N] , st[N] ;
int Node[M] , Next[M] , Head[N] , tot = 1 ;
int n , m , lim , Cnt , ans ;

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

void Tarjan( int x , int fa ) {
    S[++S[0]] = x ;
    DFN[x] = LOW[x] = ++ Tim ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( p == (fa ^ 1) ) continue ;
        if ( !DFN[Node[p]] ) {
            Tarjan( Node[p] , p ) ;
            LOW[x] = min( LOW[x] , LOW[Node[p]] ) ;
        } else LOW[x] = min( LOW[x] , DFN[Node[p]] ) ;
    }
    if ( DFN[x] == LOW[x] ) {
        ++ Cnt ;
        while ( S[0] ) {
            Belong[S[S[0]]] = Cnt ;
            Size[Cnt] ++ ;
            Cir[Cnt][Size[Cnt]] = S[S[0]] ;
            from[S[S[0]]] = Size[Cnt] ;
            S[0] -- ;
            if ( S[S[0]+1] == x ) break ;
        }
    }
}

void PreDFS( int x ) {
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( vis[Node[p]] ) continue ;
        vis[Node[p]] = 1 ;
        int u = Belong[x] , v = Belong[Node[p]] ;
        if ( u != v ) {
            G[u].push_back(v) ;
            st[v] = Node[p] ;
        }
        PreDFS( Node[p] ) ;
    }
}

void DFS( int x , int fa ) {
    for (int i = 0 ; i < (signed)G[x].size() ; i ++ )
        DFS( G[x][i] , x ) ;
    for (int i = 1 ; i <= Size[x] ; i ++ ) {
        int now = Cir[x][i] , flag = 0 ;
        h[now][1] = 1 ;
        for (int p = Head[now] ; p ; p = Next[p] ) {
            if ( Belong[Node[p]] == Belong[now] || Belong[Node[p]] == fa ) continue ;
            if ( flag == 0 ) {
                flag = 1 ;
                for (int j = 1 ; j < lim ; j ++ ) {
                    h[now][j+1] = f[Belong[Node[p]]][j] ;
                }
            } else {
                for (int j = lim ; j >= 1 ; j -- ) {
                    if ( h[now][j] == 0 ) continue ;
                    for (int k = 1 ; k <= lim - j ; k ++ ) {
                        h[now][j+k] = (h[now][j+k] + f[Belong[Node[p]]][k] * h[now][j] % MO) % MO ;
                    }
                }
            }
        }
    }
    int l = from[st[x]] ;
    int r = l + Size[x] - 1 ;
    for (int i = r ; i >= l ; i -- ) {
        int now = Cir[x][i] ;
        if ( i < r ) {
            int last = Cir[x][i+1] ;
            for (int j = 1 ; j <= lim ; j ++ ) {
                if ( h[now][j] == 0 ) continue ;
                for (int k = 1 ; j + k <= lim ; k ++ ) {
                    g[now][j+k] = (g[now][j+k] + h[now][j] * g[last][k] % MO) % MO ;
                }
            }
        } else {
            for (int j = 1 ; j <= lim ; j ++ ) g[now][j] = h[now][j] ;
        }
    }
    //f[x][1] = 1 ;
    for (int i = r - 1 ; i >= l ; i -- ) {
        int now = Cir[x][i] ;
        int last = Cir[x][i+1] ;
        for (int j = 1 ; j <= lim ; j ++ ) g[now][j] = (g[now][j] + g[last][j]) % MO ;
    }
    for (int i = l ; i <= r ; i ++ ) {
        int now = Cir[x][i] ;
        if ( i == l ) {
            for (int j = 1 ; j <= lim ; j ++ ) f1[now][j] = h[now][j] ;
        } else {
            int pre = Cir[x][i-1] ;
            for (int j = 1 ; j <= lim ; j ++ ) {
                if ( h[now][j] == 0 ) continue ;
                for (int k = 1 ; j + k <= lim ; k ++ ) {
                    f1[now][j+k] = (f1[now][j+k] + h[now][j] * f1[pre][k] % MO) % MO ;
                }
            }
        }
        if ( Size[x] == 1 ) {
            for (int j = 1 ; j <= lim ; j ++ ) f[x][j] = f1[now][j] ;
        } else {
            if ( i + 2 <= r ) {
                int next = Cir[x][i+2] ;
                for (int j = 1 ; j <= lim ; j ++ ) f[x][j] = (f[x][j] + f1[now][j]) % MO ; 
                for (int j = 1 ; j <= lim ; j ++ ) {
                    if ( f1[now][j] == 0 ) continue ;
                    for (int k = 1 ; j + k <= lim ; k ++ ) {
                        f[x][j+k] = (f[x][j+k] + f1[now][j] * g[next][k] % MO) % MO ;
                    }
                }
            } else {
                for (int j = 1 ; j <= lim ; j ++ ) f[x][j] = (f[x][j] + f1[now][j]) % MO ;
            }
        }
    }
    for (int i = l + 1 ; i <= r ; i ++ ) {
        int now = Cir[x][i] ;
        f2[now][0] = 1 ;
        if ( i == l + 1 ) {
            for (int j = 1 ; j <= lim ; j ++ ) f2[now][j] = h[now][j] ;
        } else {
            int pre = Cir[x][i-1] ;
            for (int j = 1 ; j <= lim ; j ++ ) {
                if ( h[now][j] == 0 ) continue ;
                for (int k = 0 ; j + k <= lim ; k ++ ) {
                    f2[now][j+k] = (f2[now][j+k] + h[now][j] * f2[pre][k] % MO) % MO ;
                }
            }
        }
        for (int j = 1 ; j <= lim ; j ++ ) {
            ans = (ans + f2[now][j]) % MO ;
        }
    }
    for (int i = 1 ; i <= lim ; i ++ ) ans = (ans + f[x][i]) % MO ;
}

int main() {
    freopen( "cactus.in" , "r" , stdin ) ;
    freopen( "cactus.out" , "w" , stdout ) ;
    scanf( "%d%d%d" , &n , &m , &lim ) ;
    for (int i = 1 ; i <= m ; i ++ ) {
        int u , v ;
        scanf( "%d%d" , &u , &v ) ;
        link( u , v ) ;
        link( v , u ) ;
    }
    for (int i = 1 ; i <= n ; i ++ ) {
        if ( !DFN[i] ) Tarjan( i , 1 ) ;
    }
    for (int i = 1 ; i <= Cnt ; i ++ ) {
        for (int j = 1 ; j <= Size[i] ; j ++ ) Cir[i][Size[i]+j] = Cir[i][j] ;
    }
    vis[1] = 1 ;
    PreDFS( 1 ) ;
    st[Belong[1]] = 1 ;
    DFS( Belong[1] , 0 ) ;
    printf( "%d\n" , ans ) ;
}

以上.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值