思路非常精妙的一道虚树题
简单题意:给一张图,求这张图上的独立集数量
对于一棵树的情况,可以设\(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; }