【[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))可以合并

于是我们就只需要枚举所有的非树边其连接的( m 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
", Ans % P ) ;
	return 0;
}
原文地址:https://www.cnblogs.com/Soulist/p/11637293.html