• CF848D Shake It!


    CF848D Shake It!

    给定 (n,m),现在按照此规则生成无向图,初始我们有一张图 (G),仅包含两个节点 (s,t),以及连接 (s o t) 的边。

    每次你可以选择一条已经存在的边 ((u,v)),然后将往图中加入节点 (w) 和边 ((u,w),(w,v)),这样操作 (n) 次,我们将得到一张图 (G')

    定义 (G') 合法,当且仅当 (min cut(s,t)=m),即 (s o t) 的最小割为 (m)

    求本质不同的合法的图 (G') 的数量,答案对 (10^9+7) 取模。

    其中,两张图 (G_1,G_2) 不同,当且仅当不存在一种作用于点集的置换,使得边对应相同,同时 (s,t) 对应相同。

    (n,mle 50)

    translated by EmptySoulist

    Solution

    方便起见,我们认为一次给 ((u,v)) 加入 (w) 的操作为 "加点/添加节点" 操作,我们使用 ((u o w o v)) 来描述此操作。

    同时,不难通过观察发现加点有且仅有两种方向(Left and Right)。

    请注意,此图上允许对一条边重复添加节点,这非常重要。

    我们不难观察到这张图一定是平面图,平面图最小割等于对偶图最短路。

    如果不考虑给一条边重复添加节点的情况,我们发现增加节点的过程在对偶图上非常类似于树的形态,考虑一棵树,儿子有 (0,1) 两种颜色( (0) 表示从左边加,(1) 表示右边加)每次增加节点的时候我们给其赋予一个颜色即可。

    不难发现此时对偶图最短路即这棵树深度最小的儿子的深度。

    接着我们再考虑,允许重复添加节点的情况,我们假设 ((u o v)) 的边被加入了两次 ((u o w_1 o v,u o w_2 o v)),同时 (u o w_1) 等边也被加入过点。

    我们发现这张平面图上这些边的 内外 顺序是可以任意调控的,这间接使得我们想到,答案应该是只考虑 (u o w_1 o v) 的边存在的情况和 (u o w_2 o v) 存在的情况下答案的和。

    通过简单的画图也可以说明这件事。下面将给出一个例子:

    第二张图对应的对偶图形如如此:

    (保留了 (s o t) 的边方便观看)

    其中 (A o G) 的最短路即为答案。

    进一步简化,两张图对应的对偶图分别为:

    第二张图删去了与 (G) 的连边。

    注意到这两张图本质上是相同的,也出于进一步的观察,我们不难发现我们可以对真实的对偶图进行一步简化,在初始建图中,假设某条边被操作了 (k) 次(且方向相同)我们可以类似于没有边被重复操作时的例子,直接建成树的形态,将这 (k) 条边同时挂在 (u o v) 这条边对应的点之下,假设这些边的方向均为 Left,我们发现答案即为这些独立树的答案和!

    证明的话画一下图就知道了(具体论证有点麻烦,我不太方便画图 TAT)。

    假设 Left 为颜色 (0),Right 为颜色 (1),那么我们不难得到对一棵简化的树计算答案的方法:

    [f_x=min(sum_{x o v,vin extrm{left}} f_v,sum_{x o v,vin extrm{right}} f_v)+1 ]

    由于初始根没有方向区分,所以我们只能将这张图建成森林,其贡献即为各棵子树根节点的 (f) 之和 (+1)

    我们发现本质不同的图,等价于这样简化后的树不同。

    那么考虑对树进行计数,我们设 (g_{i,j}) 表示 (i) 个节点的树,其根节点 (f) 值为 (j) 的树的方案数。

    我们发现本质不同其实说明儿子任意交换对答案没有影响,那么这里类似于无排列规则的计数问题。比如两棵树形如 (1,2,2),那么 (212,122,221) 均被视为相同的排列。

    于是我们对 left 和 right 分别进行转移,然后通过一次类似于卷积的形式合并答案即可。

    其中,left 和 right 在转移的过程使用的数组相同,我们发现问题的核心点在于如何去重上了,我们使用 (f) 数组来表示我们需要的答案。

    假设从小到大加入元素,此时 (i) 是我们最后加入的元素,对于 (i,j) 不同的元素,我们从小到大枚举 (j),这样我们保证了 ((i,j)) 的对子是按照 (i) 为第一关键字,(j) 为第二关键字从小到大加入集合的。

    对于 ((i,j)) 相同的对子,我们发现问题等价于有 (g_{i,j}) 个数,每个数出现次数任意,如果所有数出现次数相同那么算同一种方案,求方案数。

    假设 (g_{i,j}) 被使用了 (k) 次,那么不难发现贡献即 (inom{g_{i,j}+k-1}{k}),即 (frac{1}{(1-x)^k}[x^{g_{i,j}}]),是经典模型,这样的话我们需要枚举 (k)

    每次转移得到 (f) 之后,我们不难转移 (g)

    (g_{i,j}leftarrow sum_{u+v=i-1,min(l,r)=j-1}f_{u,l}f_{v,r})

    这样我们一边递推 (g),一遍递推 (f) 即可。

    边界为 (g_{1,1}=1),同时不难发现我们需要的答案即为 (f_{n,m-1})

    复杂度为 (mathcal O(n^4ln n))

    枚举顺序上有一些坑,建议多想想后写。

    (Code:)

    #include<bits/stdc++.h>
    using namespace std ;
    #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
    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 = 1e9 + 7 ; 
    const int N = 50 + 5 ; 
    int n, m, g[N][N], f[N][N], fac[N], inv[N], D[N][N][N] ;
    int fpow(int x, int k) {
    	int ans = 1, base = x ;
    	while(k) {
    		if(k & 1) ans = ans * base % P ;
    		base = base * base % P, k >>= 1 ;
    	} return ans ;
    }
    void inc(int &x, int y) {
    	((x += y) >= P) && (x -= P) ; 
    }
    int C(int x, int y) {
    	int ans = 1 ;
    	rep( i, 1, y ) ans = ans * (x - i + 1) % P ; 
    	return ans * inv[y] % P ; 
    }
    signed main()
    {
    	n = gi(), m = gi() ; fac[0] = inv[0] = 1 ; 
    	rep( i, 1, n ) fac[i] = fac[i - 1] * i % P, inv[i] = fpow( fac[i], P - 2 ) ; 
    	g[0][0] = f[0][0] = 1 ; 
    	for(re int i = 1; i <= n; ++ i) {
    		for(re int u = 0; u < i; ++ u) { 
    			int v = i - u - 1 ; 
    			rep( l, 0, u ) rep( r, 0, v )
    				inc( g[i][min(l, r) + 1], f[u][l] * f[v][r] % P ) ; 
    		}
    		rep( k, 1, n / i ) rep( j, 1, i ) D[k][i][j] = C(g[i][j] + k - 1, k) ; 
    		rep( j, 1, i ) drep( x, i, n ) rep( k, 1, x / i )  {
    			int d = D[k][i][j] ; 
    			rep( li, k * j, x ) inc( f[x][li], f[x - i * k][li - k * j] * d % P ) ;
    		}
    	}
    	cout << f[n][m - 1] << endl ; 
    	return 0 ;
    }
    
  • 相关阅读:
    MySQL学习笔记:coalesce
    Oracle学习笔记:decode函数
    MySQL学习笔记:like和regexp的区别
    状态图
    构件图和部署图
    java基础知识(一)
    包图
    活动图
    协作图
    序列图
  • 原文地址:https://www.cnblogs.com/Soulist/p/13747265.html
Copyright © 2020-2023  润新知