6821. 【2020.10.08提高组模拟】winner(xxx)

2 篇文章 0 订阅

题目描述

在这里插入图片描述

有关图的连通性统计问题

  • 这一类题目之前一直是我的天敌。见一次爆一次(也不知道为什么)。
  • 考虑设一个DP f [ i ] f[i] f[i]表示含 i i i个点的连通块的方案数。
  • 直接算的话比较麻烦,我们可以拿总方案数减去不是连通块的方案数。
  • 我们枚举第 i i i个点所在的连通块的大小 k ( 1 < = k < i ) k(1<=k<i) k(1<=k<i)
  • 很明显我们要从前 i − 1 i-1 i1个点中选出 k − 1 k-1 k1个点,即 ( k − 1 i − 1 ) (^{i-1}_{k-1}) (k1i1),这个连通块的贡献为 f [ k ] f[k] f[k],剩下的 i − k i-k ik个点随便连。
  • 所以 f [ i ] = 2 C i 2 − ∑ k = 1 i − 1 f [ k ] ∗ ( k − 1 i − 1 ) ∗ 2 C i − k 2 f[i]=2^{C_i^2}-\sum_{k=1}^{i-1}{f[k]*(^{i-1}_{k-1})*2^{C_{i-k}^2}} f[i]=2Ci2k=1i1f[k](k1i1)2Cik2

题解

  • 题目可以转化为,对于一个集合 X X X
  • 一个点 i i i存在,当且仅当 X X X中有集合包含元素 i i i
  • 一个点 i i i向点 j j j连边,当且仅当 i , j i,j i,j同时在 X X X的一个集合出现过。
  • 这个图的联通块数= g ( X ) g(X) g(X)
  • 那么我们就可以很愉快地进行连通性DP了。
  • 我们先求出 g [ i ] g[i] g[i]表示包含 i i i个点的集合有多少个。
  • 然后再用 g [ i ] g[i] g[i]来求出 f [ i ] f[i] f[i]表示包含i个点的图为一个连通块的方案数。(注意,这里的 f f f和刚才的求法有一些细节上的差别,但本质相同)
  • 最后是求答案的DP a n s [ i ] [ j ] ans[i][j] ans[i][j],表示 i i i个点组成 j j j个联通块的方案数。
  • 其实和求 f f f有异曲同工之妙。
  • 所以 a n s [ i ] [ j ] = ∑ k = 1 i − 1 a n s [ i − k ] [ j − 1 ] ∗ ( k − 1 i − 1 ) ∗ g [ k ] ans[i][j]=\sum_{k=1}^{i-1}{ans[i-k][j-1]*(^{i-1}_{k-1})*g[k]} ans[i][j]=k=1i1ans[ik][j1](k1i1)g[k]
  • 最后的答案 ∑ i = 1 n a n s [ i ] [ K ] ∗ ( i n ) \sum_{i=1}^n{ans[i][K]*(^{n}_{i})} i=1nans[i][K](in)

代码

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1e8+7,N=1e3+100,maxn=1000;
int f[N],C[N][N],g[N],h[N][N],fl[N],nf[N];
int mi(int x,int t){
	int d=1;
	while(t){
		if(t%2) d=(ll)d*x%mod;
		x=(ll)x*x%mod;t/=2;
	}
	return d;
}
int ni(int x) {return mi(x,mod-2);}
int main()
{
	fl[0]=1;for(int i=1;i<=maxn;i++) fl[i]=(ll)fl[i-1]*i%mod;
	for(int i=0;i<=maxn;i++) nf[i]=ni(fl[i]);
	for(int i=0;i<=maxn;i++) C[i][0]=C[i][i]=1;
	for(int i=2;i<=maxn;i++)
		for(int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	int n,k;
	scanf("%d%d",&n,&k);
	f[0]=1;
	for(int i=1;i<=n;i++){
		int D=1;
		for(int j=1;j<=i;j++) D=(ll)D*2%(mod-1);
		D=(D+mod-2)%(mod-1);
		f[i]=mi(2,D);
		for(int j=0;j<i;j++)
			f[i]=(f[i]-(ll)f[j]*C[i][j]%mod+mod)%mod;
	}
	for(int i=1;i<=n;i++){
		g[i]=f[i];
		for(int j=1;j<i;j++)
			g[i]=(g[i]-(ll)g[j]*C[i-1][j-1]%mod*f[i-j]%mod+mod)%mod;
	}
	h[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=k&&j<=i;j++)
			for(int p=1;p<=i-j+1;p++)
				h[i][j]=(h[i][j]+(ll)h[i-p][j-1]*g[p]%mod*C[i-1][p-1])%mod;
	int Sum=0;
	for(int i=1;i<=n;i++) Sum=(Sum+(ll)h[i][k]*C[n][i])%mod;
	printf("%d\n",Sum);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值