HDU 6691 Minimum Spanning Trees

124 篇文章 0 订阅
3 篇文章 0 订阅

题目
题意:
对于一个n个点的图,每对点 u , v u,v u,v间有 p 0 p_0 p0的几率没有边, p 1 p_1 p1的几率有权值为1的边, p 2 p_2 p2…边权 &lt; = 4 &lt;=4 <=4,对于 n − 1 &lt; = t &lt; = ( n − 1 ) ( m a x v a l   o f   e d g e ) n-1&lt;=t&lt;=(n-1)(maxval \ of \ edge) n1<=t<=(n1)(maxval of edge)的每个 t t t求有最小生成树且最小生成树大小为 t t t的几率 ( m o d 1000000007 ) \pmod {1000000007} (mod1000000007)

考虑克鲁斯卡尔求生成树的过程,那么我们可以从小到大加入边,在 d p dp dp上即为 f i , j f_{i,j} fi,j表示 &lt; = i &lt;=i <=i的边联通特定的 j j j个点的概率。
发现不好转移,但是还是可以转移的。
枚举特定的点中标号最小的点所在的由 &lt; = i − 1 &lt;=i-1 <=i1的边联通的极大联通块大小,这个联通块往外连的边中考虑边权为 i i i的边,它连向了一个由 &lt; = i &lt;=i <=i的边联通的且不包含当前联通块的极大联通块,这个联通块是比标号最小的点所在的由 &lt; = i &lt;=i <=i的边联通的极大联通块小的,是一个子问题,那么它的方案数是可以在之前就计算出来的,相当于一个点有很多个儿子,我们从小到大枚举儿子子树大小然后枚举这个大小的有多少个再组合数分配标号去重,这样就可以转移了。
但是我们这样只算了联通的概率,并没有计算最小生成树的值。。。。。。
那么就再来一维?
我们用生成函数表示最后一维, x t x^t xt前的系数表示权值为 t t t的最小生成树的方案。
因为我们可以发现我们之前这个究极复杂的DP是可以算最小生成树的值的。
然后用 x = 0... ( n − 1 ) ( m a x v a l   o f   e d g e − 1 ) x=0...(n-1)(maxval \ of \ edge - 1) x=0...(n1)(maxval of edge1)带入计算答案然后插值得到多项式即可。

原题解(有几处(官方)错误):
f i , j f_{i,j} fi,j 表示 i 个点的图由权值$ ≤ t $的边连通的概率,初值 f 0 , 1 = 1 ; f 0 , j = 0 ( 2 ≤ j ≤ n ) f_{0,1} = 1; f_{0,j} = 0 (2 ≤ j ≤ n) f0,1=1;f0,j=0(2jn)
假设 f i , j ( 0 ≤ i ≤ t − 1 ; 1 ≤ j ≤ n ) f_{i,j} (0 ≤ i ≤ t − 1; 1 ≤ j ≤ n) fi,j(0it1;1jn)均已求出,当前考虑到权值为 t 的边。考虑一个 dfs 由 ≤ t 的边
构成的连通块的过程:从 1 号点所在的由 ≤ t − 1 ≤ t − 1 t1的边构成的连通块出发,按照从小到大的顺序递归访
问其他由 ≤ t 的边构成的连通块(大小相同则优先访问块内最小编号较小的)。现在 dp 这个 dfs 过程,
g t , i , j g_{t,i,j} gt,i,j表示从 1 号点所在的由 ≤ t − 1 ≤ t − 1 t1 的边构成的大小为 i 的连通块出发,已经访问过的其他由 ≤ t 的
边构成的连通块大小之和是 j 的概率,按照由 ≤ t ≤ t t 的边构成的连通块的大小 s 从小到大考虑,先计算出
f t , s f_{t,s} ft,s,再枚举 g t , i , j g_{t,i,j} gt,i,j 挂上多少个大小为 s 的连通块,需要和挂上的连通块之间连 ≥ t ≥ t t 的边或者不连边,但
是至少有一条权值为 t 的边,并且挂上的连通块之间两两不能连权值 ≤ t ≤ t t 的边。不难分析这样 dp 一次
的复杂度是 O ( k n 3 l o g n ) O(kn^3 log n) O(kn3logn)
至此还没有考虑最小生成树,为了避免在状态里额外记录权值和,每挂上一个大小为 s 的连通块就
给概率额外乘上 x s − 1 x_{s−1} xs1,最后会得到一个关于 x 的多项式,其中 x t x^t xt 项的系数就是最小生成树是 t + ( n − 1 ) t+ (n−1) t+(n1)
的概率,分别取 x = 0 ; 1 ; 2 ; 3 , ( k − 1 ) ( n − 1 ) x = 0; 1; 2; 3,(k − 1)(n − 1) x=0;1;2;3,(k1)(n1) 代入求值,最后使用插值或者高斯消元求出多项式即可。

另外标程对于 x i = i , 0 &lt; = i &lt; = n x_i=i,0&lt;=i&lt;=n xi=i,0<=i<=n的拉格朗日插值求多项式的方法十分巧妙(我精简后的代码只有7行)。
对于把 0 0 0也带入的连续整数的拉格朗日插值。
对于 y j y_j yj后面乘上的式子的分母不是 1 1 1开始的那种 ( − 1 ) n − j ( j − 1 ) ! ( n − j ) ! (-1)^{n-j}(j-1)!(n-j)! (1)nj(j1)!(nj)!,而是 ( − 1 ) n − j j ! ( n − j ) ! (-1)^{n-j}j!(n-j)! (1)njj!(nj)!
这样我们就可以用巧妙的累和而不是直接计算求出系数。

  for(int   j=n;j&gt;=i;j--)   a[j]=1ll*(a[j]-a[j-1])*inv[i] \texttt{ for(int j=n;j&gt;=i;j--) a[j]=1ll*(a[j]-a[j-1])*inv[i]}  for(int j=n;j>=i;j--) a[j]=1ll*(a[j]-a[j-1])*inv[i]
这一行中,每一个 a j a_j aj会对 a i a_i ai贡献 ( i j ) ÷ i ! ∗ a [ j ] \binom{i}{j} \div {i!} * a[j] (ji)÷i!a[j],恰好就是 a [ j ] j ! ( i − j ) ! \frac {a[j]}{j!(i-j) !} j!(ij)!a[j]

s[j]   =   ((j?s[j-1]:0)   -   (i-1ll)   *   s[j]) \texttt{s[j] = ((j?s[j-1]:0) - (i-1ll) * s[j])} s[j] = ((j?s[j-1]:0) - (i-1ll) * s[j])
这一行中, s [ j ] s[j] s[j]存的是前 i − 2 i-2 i2个数(包括 0 , 1 , 2... i − 1 0,1,2...i-1 0,1,2...i1)选择 j j j个他们的乘积和。
然后再加乘起来就刚好是答案,太巧也太强了。

#include<bits/stdc++.h>
#define maxn 41
#define mod 1000000007
using namespace std;

int n,k;
int f[5][maxn],sp[6],p[5],g[maxn][maxn],tr[maxn][maxn],tb[maxn][maxn];
int Pow(int base,int k){
	int ret = 1;
	for(;k;k>>=1,base=1ll*base*base%mod)
		if(k&1)
			ret=1ll*ret*base%mod;
	return ret;
}

int inv[maxn*maxn]={1,1},fac[maxn*maxn]={1,1},invf[maxn*maxn]={1,1},c[maxn*maxn][maxn*maxn],pw[maxn*maxn];

vector<int>interpolation(vector<int>a,int n){// only when x_i = i , 0 <= i <= n
	vector<int>s(n+1),r(n+1);
	r[0]=a[0],s[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=n;j>=i;j--) a[j]=1ll*(a[j]-a[j-1])*inv[i]%mod;
		for(int j=i;j>=0;j--)
			s[j] = ((j?s[j-1]:0) - (i-1ll) * s[j]) % mod,
			r[j] = (r[j] + s[j] * 1ll * a[i]) % mod;
	}
	return r;
}

int main(){
	int T;
	
	c[0][0] = 1;
	for(int i=1;i<maxn*maxn;i++){
		c[i][0] = 1;
		for(int j=1;j<=i;j++)
			c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;
	}
	
	for(int i=2;i<maxn*maxn;i++)
		fac[i] = 1ll * fac[i-1] * i % mod,
		inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod,
		invf[i] = 1ll * invf[i-1] * inv[i]  %mod;
		
	for(scanf("%d",&T);T--;){
		scanf("%d%d",&n,&k);
		for(int i=0;i<=k;i++) scanf("%d",&p[i]) , p[i] = p[i] * 1ll * Pow(100,mod-2) % mod;
		sp[k+1] = p[0];
		for(int i=k;i>=0;i--) sp[i] = (sp[i+1] + p[i]) % mod;
		vector<int>ar;
		for(int x=0;x<=(k-1)*(n-1);x++){	
			memset(f,0,sizeof f);
			f[0][1] = 1;
			pw[0] = 1;
			for(int i=1;i<=n;i++) pw[i] = pw[i-1] * 1ll * x % mod;
			for(int t=1;t<=k;t++){
				memset(g,0,sizeof g);
				
				for(int i=0;i<=n;i++)
					for(int j=0;j+i<=n;j++){
						tb[i][j] = Pow(sp[t+1],i*j);
						tr[i][j] = (Pow(sp[t],i*j)-tb[i][j]) % mod;
					}
					
				for(int i=1;i<=n;i++)
					g[i][0] = 1;
				for(int s=1;s<=n;s++){
					for(int i=1;i<=s;i++) f[t][s] = (f[t][s] + 1ll * g[i][s-i] * c[s-1][i-1] % mod * f[t-1][i]) % mod;
					for(int i=1;i<=n;i++)
						for(int j=n-i;j>=0;j--)
							for(int k=1,sum=1,cr=1ll*tr[i][s]*f[t][s]%mod*pw[t-1]%mod;k*s+j<=n-i;k++){
								sum = 1ll * sum * cr % mod * tb[(k-1)*s+j][s] % mod * c[k*s+j][s] % mod;
								g[i][k*s+j] = (g[i][k*s+j] + 1ll * sum * g[i][j] % mod * invf[k]) % mod;
							}
				}
			}
			
			ar.push_back(f[k][n]);
		}
		
		ar = interpolation(ar,(k-1)*(n-1));
		for(int i=0;i<=(k-1)*(n-1);i++)
			printf("%d%c",(ar[i]+mod)%mod," \n"[i==(k-1)*(n-1)]);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值