【[HNOI/AHOI2018]毒瘤】

思路非常精妙的一道虚树题

简单题意:给一张图,求这张图上的独立集数量

对于一棵树的情况,可以设\(dp_{u,0/1}\)表示节点\(u\)选/不选的方案数

显然有:

\[dp_{u,0}=\prod (dp_{v,1}+dp_{v,0})\]

\[dp_{u,1}=\prod dp_{v,0}\]

接下来考虑非树边

正常\(dp\)的话那么转移应该是\(O(n)\)的

我们发现非树边很少,所以可以预处理出来,然后枚举其连接的两点之间的状态

不难发现状态只有如下三种:\((0,0),(0,1),(1,0)\)

可以发现\((0,0)\)和\((0,1)\)可以合并

于是我们就只需要枚举所有的非树边其连接的\(\rm dfn\)较小的点的状态就可以了

复杂度\(O(n*2^{11})\)可以得到\(75pts\)的高分

接下来是一个很妙的做法:

我们可以考虑将所有非树边上的点拿下来建虚树

这应该是一个大小\(\le(44+1)\)的虚树

注意到,我们的\(dp\)转移本应如此:

\[dp_{u,0}=\prod (dp_{v,1}+dp_{v,0})\]

\[dp_{u,1}=\prod dp_{v,0}\]

但是放在虚树上这样显然是错的

我们发现虚树是维护了点之间的关系并构建了其\(lca\),所以虚树上的点和其父亲两两之间是一条链的关系

我们仔细思考可以发现这个东西居然只是额外附带了一个系数而已...

不妨设虚树上的一条边\(u,v\)为\(u->k_1->k_2...->k_r->v\),显然中间是没有其他关键点的

我们发现\(v\)对于\(u\)造成的贡献实际上是可以计算出来的

因为没有其他儿子,所以\(k_r\)本身的\(dp\)值可以计算出来,考虑\(dp_{v,0}\)对其造成的贡献,有:\(dp_{k_r,1}*=dp_{v,0},dp_{k_r,0}*=(dp_{v,0}+dp_{v,1})\)

\(dp_{k_{r-1},1}*=dp_{k_r,0}\)即\(dp_{k_{r-1},1}*=(dp_{k_r,0}'*(dp_{v,0}+dp_{v,1}))\)

\(dp_{k_{r-1},0}*=(dp_{k_r,0}+dp_{k_r,1})\)即\(dp_{k_{r-1},0}*=(dp_{k_r,0}'*(dp_{v,0}+dp_{v,1})+dp_{k_r,1}'*dp_{v,0})\)

好像这样类似下去这样的系数是可以预处理出来的欸欸欸?

于是我们可以把虚树上的转移式改成这样:

\[dp_{u,0}=\prod(k_{v,1->0}*dp_{v,1}+k_{v,0->0}*dp_{v,0})\]

\[dp_{u,1}=\prod(k_{v,1->1}*dp_{v,1}+k_{v,0->1}*dp_{v,0})\]

剩下的工作就是预处理系数了qwq

我们考虑如何求解系数,我们类比于上面展开那个式子的过程,将其中的\(dp_{kr,0/1}'\)换成到那个点时候的系数

就可以得到系数的递推式了,具体展开非常麻烦,就不写了

然后我们考虑在虚树上\(dp\),仅仅给每个点额外附上一个系数就可以了吗?

当然是不行的,每个点的\(dp\)初值也不是我们一开始所说的\(dp\)值\(1\)了

所以我们还要对虚树上的每个点做\(dp\)以计算初值,不过这一步相对简单,因为仔细考虑我们会发现如果一个点的某棵子树内有被标记的点,那么显然这个子树会被表示成虚树上的一条边,系数已经计算出来了,所以我们实际上要处理的\(dp\)值只有那些子树内没有被标记点的\(dp\)值

一道虚树的题我硬生生写了\(5kb\)....汗,不亏是[毒瘤]

不过我的代码应该还比较清晰qwq

设\(s=m-n+1\),尽管建树看似像遍历了\(s\)遍全树,但实际上只有一遍....

所以复杂度为\(O(n+s*2^s)\)

#include<bits/stdc++.h>
using namespace std;
#define Next2( i, x ) for( register int i = fire[x]; i; i = p[i].next ) 
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define int long long 
int gi() {
    char cc = getchar(); int cn = 0, flus = 1;
    while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
    while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
    return cn * flus;
}
const int P = 998244353 ; 
const int N = 1e5 + 5 ;
const int M = 55 ; 
int dp[N][2], dfn[N], head[N], fire[N], Ans, n, m, cnt ;
int num, Cnt, Fr[N], Ed[N], vis[N], s[M], top, Rev[N][2] ; 
int Fa[N], Top[N], dep[N], sz[N], Son[N], k0[N][2], k1[N][2] ; 
struct E {
    int to, next ; 
} e[N * 2], p[N * 2] ;
void add( int x, int y ) {
    e[++ cnt] = (E){ y, head[x] }, head[x] = cnt , 
    e[++ cnt] = (E){ x, head[y] }, head[y] = cnt ; 
}
void add2( int x, int y ) {
    p[++ cnt] = (E){ y, fire[x] }, fire[x] = cnt ,
    p[++ cnt] = (E){ x, fire[y] }, fire[y] = cnt ;  
}
//建树 
inline void dfs( int x, int fa ) {
    dfn[x] = ++ num, Fa[x] = fa, dep[x] = dep[fa] + 1, sz[x] = 1 ; 
    Next( i, x ) {
        int v = e[i].to ; if( v == fa ) continue ; 
        if( !dfn[v] ) {
            dfs( v, x ), sz[x] += sz[v] ;
            if( sz[v] > sz[Son[x]] ) Son[x] = v ; 
        }
        else if( dfn[v] < dfn[x] ) Fr[++ Cnt] = v, Ed[Cnt] = x, vis[v] = vis[x] = 1 ; 
    }
}
inline void dfs2( int x, int high ) {
    Top[x] = high ; 
    if( Son[x] ) dfs2( Son[x], high ), vis[x] += vis[Son[x]] ;
    Next( i, x ) {
        int v = e[i].to ; if( v == Fa[x] || v == Son[x] ) continue ; 
        dfs2( v, v ), vis[x] += vis[v] ;
    }
}
int LCA( int x, int y ) {
    while( Top[x] != Top[y] ) {
        if( dep[Top[x]] < dep[Top[y]] ) swap( x, y ) ;
        x = Fa[Top[x]] ; 
    }
    return ( dep[x] > dep[y] ) ? y : x ; 
}
int K[50], tot ; 
bool cmp( int x, int y ) {
    return dfn[x] < dfn[y] ; 
}
inline void insert( int x ) {
    if( top == 1 && x != 1 ) { s[++ top] = x ; return ; }
    int lca = LCA( s[top], x ) ; if( lca == x ) return ; 
    while( top > 1 && dfn[lca] < dfn[s[top - 1]] ) add2( s[top - 1], s[top] ), -- top ;
    if( dfn[lca] < dfn[s[top]] ) add2( lca, s[top] ), -- top ;
    if( dfn[lca] > dfn[s[top]] ) s[++ top] = lca ; 
    s[++ top] = x ; 
}
//建立虚树 
void Maker() {
    cnt = 0 ;
    rep( i, 1, Cnt ) K[++ tot] = Fr[i], K[++ tot] = Ed[i] ; 
    sort( K + 1, K + tot + 1, cmp ) ; s[top = 1] = 1 ; 
    rep( i, 1, tot ) insert( K[i] ) ;
    while( top > 1 ) add2( s[top - 1], s[top] ), -- top ;
}
void init() {
    n = gi(), m = gi() ; 
    rep( i, 1, m ) add( gi(), gi() ) ; cnt = 0 ; 
    dfs( 1, 1 ), memset( head, 0, sizeof(head) ) ;
    rep( i, 1, n ) if( Fa[i] != i ) add( Fa[i], i ) ;
    dfs2( 1, 1 ), Maker() ; 
}
//辅助计算系数 
inline void DP( int x, int u ) {
    dp[x][1] = dp[x][0] = 1 ; 
    Next( i, x ) {
        int v = e[i].to ; if( v == Fa[x] || v == u ) continue ; 
        DP( v, x ), dp[x][1] *= dp[v][0], dp[x][1] %= P ;
        dp[x][0] *= ( dp[v][1] + dp[v][0] ), dp[x][0] %= P ; 
    }
}
//计算系数... 
inline void Get( int u, int fa ) {
    int v = u ; k0[u][0] = 1, k0[u][1] = 1, k1[u][0] = 1 ; 
    while( Fa[v] != fa ) {
        DP( Fa[v], v ) ; int r0 = k0[u][0], r1 = k0[u][1] ;
        k0[u][0] = ( dp[Fa[v]][0] * r0 + dp[Fa[v]][1] * k1[u][0] ) % P ;
        k0[u][1] = ( dp[Fa[v]][0] * r1 + dp[Fa[v]][1] * k1[u][1] ) % P ;
        k1[u][1] = dp[Fa[v]][0] * r1 % P, k1[u][0] = dp[Fa[v]][0] * r0 % P ;
        v = Fa[v] ; 
    }
}
//计算dp初值 
inline void Get_DP( int x, int fa ) {
    Rev[x][0] = Rev[x][1] = 1 ; 
    Next( i, x ) {
        int v = e[i].to ; if( v == fa || vis[v] ) continue ; 
        Get_DP( v, x ), Rev[x][0] *= ( Rev[v][0] + Rev[v][1] ), Rev[x][0] %= P ;
        Rev[x][1] *= Rev[v][0], Rev[x][1] %= P ;
    }
} 
//计算出每条边的系数,每个点的dp初值 
inline void dfs_init( int x, int fa ) {
    Get_DP( x, fa ), K[++ tot] = x ; 
    if( x != fa ) Get( x, fa ) ;
    Next2( i, x ) {
        int v = p[i].to ; if( v == fa ) continue ;
        dfs_init( v, x ) ; 
    }
}
//真正的dp,前面的都是预处理 
inline void Dp( int x, int fa ) {
    Next2( i, x ) {
        int v = p[i].to ; if( v == fa ) continue ; 
        Dp( v, x ) ; dp[x][0] *= ( k0[v][0] * dp[v][0] % P + k0[v][1] * dp[v][1] % P ), dp[x][0] %= P ;
        dp[x][1] *= ( k1[v][0] * dp[v][0] % P + k1[v][1] * dp[v][1] % P ), dp[x][1] %= P ;
    }
}
signed main()
{
    init(), tot = 0, dfs_init( 1, 1 ) ; 
    int maxn = ( 1 << Cnt ) - 1 ;
    rep( i, 0, maxn ) {
        rep( i, 1, tot ) dp[K[i]][0] = Rev[K[i]][0], dp[K[i]][1] = Rev[K[i]][1] ; 
        for( re int j = 1; j <= Cnt; ++ j ) {
            if( ( 1 << ( j - 1 ) ) & i ) dp[Fr[j]][0] = 0, dp[Ed[j]][1] = 0 ; 
            else dp[Fr[j]][1] = 0 ; 
        }
        Dp( 1, 1 ), Ans = ( Ans + dp[1][0] + dp[1][1] ) % P ;
    }
    printf("%d\n", Ans % P ) ;
    return 0;
}
上一篇:HNOI 排队


下一篇:[HNOI/AHOI2018]排列