• 【[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;
    }
    
  • 相关阅读:
    以太坊客户端geth的基本操作命令
    以太坊虚拟机介绍5-比较操作指令
    以太坊虚拟机介绍4-按位运算指令
    浏览器内核
    javascript 几个易错点记录
    jq常用方法
    jq事件操作汇总
    js事件总汇
    margin 负边距 的知识点
    子div撑不开父div的几种解决办法:
  • 原文地址:https://www.cnblogs.com/Soulist/p/11637293.html
Copyright © 2020-2023  润新知