zoukankan      html  css  js  c++  java
  • 【UR #17】滑稽树前做游戏

    C 【UR #17】滑稽树前做游戏 [* hard]

    给定一张 (n) 个点的图,每个点的点权在 ([0,1]) 中随机(随机实数),每条边的边权定义为连接的两个点的点权和,求点权和边权中的最大值的期望。答案对 (998244353) 取模。

    (nle 25,mle frac{n(n-1)}{2})

    Solution

    好神奇啊,原来图计数可以这样搞。

    考虑设 (g(x)=P(Xle x)),那么 (p(x)=g'(x))

    所以答案:

    [int_0^2 dxcdot p(x)=2-int_0^2 dxcdot g(x) ]

    考虑维护 (int_0^2 dxcdot g(x))

    首先可以明确的一点是,答案是通过积分得到的多项式。

    (f(G,t,y)) 表示对于图 (G),每个点的点权在 ([0,t]) 中随机,点权和边权的最大值小于 (y) 的概率多项式,那么可以得到很神奇的转移:

    • 所有点权值均小于 (frac{y}{2}),此事发生的概率为 ((frac{y}{2})^{|G|})
    • 存在一个点大于 (frac{y}{2}),那么不妨枚举最大值是谁,设最大值为 (w),对应的节点为 (i),那么此时与 (i) 相连的点的权值均在 ((y-w)) 中随机,然后由于他们的出边中的其他点权值都小于 (i),所以这些点对于答案已经没有贡献了,我们可以直接删掉,设删除 (i) 以及 (i) 的出边的点集为 (G_i),此时我们得到转移:

    [int_{frac{y}{2}}^t dwcdot f(G_i,w,y) imes (y-w)^{|deg(i)|} ]

    综上,我们得到转移式为:

    [f(G,t,y)=(frac{y}{2})^{|G|}+sum_{iin G}(y-w)^{deg(i)} imes int_{frac{y}{2}}^t dwcdot f(G_i,w,y) ]

    于是一个暴力做法就是暴力维护关于 (t,y) 的二元多项式并进行积分,暴力乘法,复杂度为 (mathcal O(2^ncdot n^5)),似乎并不能跑啥东西 /ll

    但是可以注意到 (t^icdot y^{j}) 总是满足 (i+j=|G|)(归纳一下即可),所以可以只维护 (n) 项来进行操作。复杂度降低成了 (mathcal O(2^ncdot n^3))

    能否更优呢,我们搞一些神奇的优化:

    • 假设删除一个点之后,图不连通,那么可以直接将答案乘在一起。

    可以考虑假设连通,那么每次操作都会删除很多点的样子...总之就是非常优的样子...?

    最后,我们回想起我们的上述做法似乎默认了 (wle y) 同时 (wle 1) 了。

    所以答案应该为:

    [2-int_0^1 dycdot f(G,y,y)-int_1^{2} dycdot f(G,1,y) ]

    当然,有趣的是 (y) 在转移的过程几乎是定值,只有最后算答案是以 (y) 为主元的积分,所以可以全程先维护关于 (t) 的多项式((y) 的次幂均为 (cnt-i))最后再操作一番。

    具体积分过程:

    [int_{frac{y}{2}}^t dwcdot f(G_i,w,y) ]

    等价于 (F(w=t)-F(w=frac{y}{2})),其中 (w=frac{y}{2}) 时,每个项都会贡献到 (y^{|G|}) 处即 (w^0) 处,预处理 (frac{1}{2^k}) 即可。

    代码比较不受人待见:

    (Code:)

    #include<bits/stdc++.h>
    using namespace std ;
    #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 rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
    #define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
    #define re register
    #define int long long
    #define blz(x) __builtin_popcountll(x)
    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 inv2 = (P + 1) >> 1 ; 
    const int N = 31 ;
    int fpow(int x, int k) {
    	int ans = 1, base = x ;
    	while(k) {
    		if(k & 1) ans = 1ll * ans * base % P ;
    		base = 1ll * base * base % P, k >>= 1 ;
    	} return ans ;
    }
    int n, m, G[N], st[N], top ;
    struct mint {
    	int x ; 
    	mint(int _x = 0) { x = _x ; }
    	void it() { x = 0 ; }
    } C[N][N], inv[N], fc[N], iv[N] ;
    mint operator * (mint a, int b) { return a.x * b % P ; }  
    mint operator * (mint a, mint b) { return a.x * b.x % P ; }  
    mint operator - (mint a, mint b) { return ((a.x -= b.x) < 0) ? a.x + P : a.x ; }
    mint operator + (mint a, mint b) { return ((a.x += b.x) >= P) ? a.x - P : a.x ; }
    struct node {
    	int cnt ; mint a[N] ; 
    	void fint() {
    		drep( j, 1, cnt + 1 ) a[j] = a[j - 1] * inv[j] ;
    		a[0].it(), ++ cnt ; 
    	}
    	void init() { cnt = 0 ; rep( i, 0, 27 ) a[i].it() ; }
    	void init(int _x) { cnt = _x ; rep( i, 0, 27 ) a[i].it() ; }
    } ;
    unordered_map<int, node> f ; 
    node operator * (node a, node b) {
    	node z ; z.cnt = a.cnt + b.cnt ; 
    	rep( i, 0, a.cnt ) rep( j, 0, b.cnt ) 
    		z.a[i + j] = z.a[i + j] + a.a[i] * b.a[j] ; 
    	return z ; 
    }
    node operator - (node a, node b) {
    	node z ; z.cnt = a.cnt ; 
    	rep( i, 0, a.cnt ) z.a[i] = a.a[i] - b.a[i] ;
    	return z ; 
    }
    node operator + (node a, node b) {
    	node z ; z.cnt = a.cnt ; 
    	rep( i, 0, a.cnt ) z.a[i] = a.a[i] + b.a[i] ;
    	return z ;  
    }
    void init() {
    	C[0][0].x = inv[0].x = fc[0].x = iv[0].x = 1 ;
    	rep( i, 1, 27 ) {
    		C[i][0].x = 1 ; 
    		rep( j, 1, 27 ) C[i][j] = C[i - 1][j - 1] + C[i - 1][j] ; 
    	}
    	rep( i, 1, 27 ) 
    		inv[i].x = fpow(i, P - 2), 
    		fc[i] = fc[i - 1] * 2, iv[i] = iv[i - 1] * inv2 ; 
    }
    node upd(node x, int deg) {
    	node t ; t.init(deg) ;
    	rep( i, 0, deg ) t.a[i] = C[deg][i] * ((i & 1) ? P - 1 : 1) ;
    	x = x * t, x.fint(), t.init(x.cnt) ;
    	rep( i, 0, x.cnt ) t.a[i] = t.a[i] + x.a[i], t.a[0] = t.a[0] - x.a[i] * iv[i] ;
    	return t ; 
    }
    void dfs(int u, int S, int &vis) {
    	if( (1 << u) & vis ) return ; vis |= (1 << u), st[++ top] = u ; 
    	Rep( i, 0, n ) if(((1 << i) & S) && ((1 << i) & G[u])) dfs(i, S, vis) ; 
    }
    node F(int lim) {
    	if( f.count(lim) ) return f[lim] ; 
    	node z ; z.init(blz(lim)), z.a[0] = iv[blz(lim)] ; 
    	int num = 0, vis = 0 ; 
    	Rep( i, 0, n ) 
    		if( ((1 << i) & lim) && (!(vis & (1 << i))) ) top = 0, ++ num, dfs(i, lim, vis) ; 
    	if( num > 1 ) {
    		z.init(), z.a[0].x = 1 ; vis = 0 ; 
    		Rep( i, 0, n ) {
    			if( (!((1 << i) & lim)) || (vis & (1 << i)) ) continue ; 
    			top = 0, dfs(i, lim, vis) ; int k = 0 ; 
    			rep( j, 1, top ) k |= (1 << st[j]) ;
    			z = z * F(k) ; 
    		}
    		f[lim] = z ; return z ; 
    	}
    	Rep( i, 0, n ) {
    		if( !((1 << i) & lim) ) continue ; 
    		int deg = blz(G[i] & lim) - 1 ;
    		int S = lim ^ (lim & G[i]) ;
    		node d = F(S) ; d = upd(d, deg), z = z + d ; 
    	}
    	f[lim] = z ; return z ; 
    }
    signed main()
    {
    	n = gi(), m = gi() ; int x, y ; 
    	Rep( i, 0, n ) G[i] |= (1 << i) ;  
    	rep( i, 1, m ) {
    		x = gi() - 1, y = gi() - 1 ; 
    		G[x] |= (1 << y), G[y] |= (1 << x) ; 
    	}
    	init() ; int limit = (1 << n) - 1 ; mint ans, sum ; ans.it(), sum.it() ; 
    	ans.x = 2 ; node g = F(limit) ; 
    	int l = g.cnt ; 
    	rep( i, 0, l ) sum = sum + g.a[i] ;
    	sum = sum * inv[l + 1], ans = ans - sum ; 
    	rep( i, 0, l ) {
    		int u = l - i + 1 ; 
    		mint t = g.a[i] ; t = t * inv[u] ; 
    		ans = ans - t * fc[u] + t ; 
    	}
    	cout << ans.x << endl ; 
    	return 0 ;
    }
    
  • 相关阅读:
    斐波拉契数列
    判断润年
    欧拉回路
    走迷宫
    八连块问题
    知道一棵二叉树的前序和中序序列求二叉树的后续序列
    判断一个顺序排列的栈的输出序列
    Number Sequence
    如何去设计一个自适应的网页设计或HTMl5
    position
  • 原文地址:https://www.cnblogs.com/Soulist/p/13881872.html
Copyright © 2011-2022 走看看