How Many of Them|计数类dp|题解

题目链接
这个题真挺抽象的.

解析

题意非常清晰,不过多赘述.
首先思考这题,从dp的角度去入手.
先构建状态,以\(f_{i,j}\)表示在i个节点构成的无向连通图中有j条割边(毕竟题目要求我们求的就是这个).
但是就要想怎么转移,我们要缩小问题的规模.
将状态分割为多个部分,每个部分是一个双联通分量,链接每两个双联通的是割边.
那么我们假设,以一个双联通作为基点,大小为k,直接连向它的有x个双联通(也就是说,这一个双联通与外部有x条割边).
对于这个基点来说,我们要设一个编号作为代表元素,因为当我们把这个元素提出来的时候,这个基点就固定了.
固定的意思是指,它的状态是与其他状态互斥的,如果说我们不设代表(特征)元素,而是直接从i个节点中选出k个.
我们在本次计算时是毫无影响的,因为它每层决策的选取是固定的(假设前几层记录的f都正确).
但是如果我们将k的大小变化一下,记作\(k'(k+k'<=i)\).
它就存在一种情况,该连通块选了\(k'\)个,底下调用状态时,底下状态包含了底层有双联通选了k个的.
那这个时候它的统计就重复了,状态就不满足互斥了.
所以我们在计数类dp中总强调要找基点,蓝书里面说是固定1(编号最小的)这个点,大概就是因为这个.
回归正题,那么我们在选出这个基点时,就应该是有\(C_{i-1}^{k-1}\)种选择方式.
构造完这个基点后可以想到,对于这个双联通来说,他有x个边连向其他双联通.
但是这些边都在哪些点上是可以自由排列的,所以这时就有\(k^x\)的排列方式.
对于这个双联通的所有情况(先不考虑其他双联通的统计时),就可以写作\(f_{k,0}×C_{i-1}^{k-1}×k^x\)
然后这剩下的i-k个点应该构成x个联通块,并且其中有j-x条割边,其中每个块有一个点连向之前的双联通.
这个情况的统计我们就先记为\(g_{i-k,j-x,x}\).
则$$f_{i,j}=∑^{i-1}_ {k=1}f_{k,0}×C_{i-1}^{k-1} ×∑_{x=1}^{min(i-k,j)} k^x×g_{i-k,j-x,x}$$
但是可以发现我们这个状态更新更新不了\(f_{i,0}\).
我们可以设一个h表示由i个节点构成的无向连通图数量.
那么\(f_{i,0}=h_i-∑_{j=1}^{i-1}f_i^j\)
然而h怎么求就成了新的问题,我们就又要想一想这个h求取的递推关系.
在这个h上,我们可以再开一个数位dp计算.
我们要求i个节点的无向连通图个数,就还是先提出来一个基点,把问题分开.
由于图联通情况不好分割,我们假设我们先求不符合条件(即为图不连通)的情况,让它来减去总情况来得到h.
如果不要求全联通,只是i个节点在图内部就行,那么所有点之间连接边的情况就是\(2^{i×(i-1)/2}\).
所以我们还是找基点,假设是有一个连通块,大小为k,当然,还是要求固定一个代表元素.
剩下的节点之间随便连边,只要不连一开始那个连通块就行,方案数是\(2^{(i-k)×(i-k-1)/2}\)
那么这种情况的方案数可以表示为\(f_k×C_{i-1}^{k-1}×2^{(i-k)×(i-k-1)/2}\)
则最后

\[h_i=2^{i×(i-1)/2}-∑^{i-1}_ {k=1}f_k×C_{i-1}^{k-1}×2^{(i-k)×(i-k-1)/2} \]

这样\(f_{i,0}\)的问题已经解决,之后就只剩下g怎么算.
求这个\(g_{i,j,k}\)我们依旧是分割状态,老套路选基点以及代表元素,假设是里面提取出来一个联通块大小为p,其中有q个割边.
这个p,q的选取明显是为了与之前已知的f扯上关系,这样能够划分好状态,将联通块数(问题规模)变少.
那么类似之前一样\(f_{p,q}×C_{i-1}^{p-1}\)表示这个连通块的选取的可能性.
但是我们要求是要让连通块内的一个节点连向之前的双联通,所以这个情况要乘上一个p.
这个时候剩下的问题又是个子问题,就是\(g_{i-p,j-q,k-1}\).
那么

\[g_{i,j,k}=∑_{p=1}^{i-k+1}∑_{q=0}^{min(j,p-1)}f_{p,q}×C_{i-1}^{p-1}×p×g_{i-p,j-q,k-1} \]

OK,这样f就可以递推出来.
最后的\(ans=∑_{i=0}^{m}f_{n,i}\)

后寄

然而由于太蒻不知道在luogu中题解的神犇们的各种骚操作,什么牛顿迭代还有二项式反演之类的.
所以先预留一个位置吧,没准之后在学完那些东西后回来再更新一下这个题解.

#include<bits/stdc++.h>
#define ll long long
#define lc tree[rt].lc
#define rc tree[rt].rc
#define pa pair<int,int>
#define R register
#define qr qr()
using namespace std;
const ll N=2e6+200,MN=5000,mod=1e9+7;
ll n,m,c[60][60],f[60][60],g[60][60][60],h[60];
inline int qr
{
	int x=0;bool f=0;char ch=getchar();
	while(ch>57||ch<48)
	{
		if(ch=='-')f=1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57)x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
ll qp(ll base,ll p)
{
	ll ans=1;
	while(p)
	{
		if(p&1)ans=ans*base%mod;
		base=base*base%mod;
		p>>=1;
	}return ans;
}
void init()
{
	n=qr;m=qr;
	// if(m>n){
	// 	printf("0");
	// 	exit(0);
	// }
	c[0][1]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=i+1;++j)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	g[0][0][0]=1;
	for(int i=1;i<=n;++i){
		h[i]=qp(2,i*(i-1)/2);
		for(int j=1;j<i;++j)
			h[i]=(h[i]-h[j]*c[i-1][j]%mod*qp(2,(i-j)*(i-j-1)/2)%mod+mod)%mod;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<i;++j)
			for(int k=1;k<i;++k){
				ll tmp=0;
				for(int x=1;x<=min(i-k,j);++x)
					tmp=(tmp+g[i-k][j-x][x]*qp(k,x)%mod)%mod;
				f[i][j]=(f[i][j]+f[k][0]*c[i-1][k]%mod*tmp%mod)%mod;
			}
		f[i][0]=h[i];
		for(int j=1;j<i;++j)f[i][0]=((f[i][0]-f[i][j])%mod+mod)%mod;
		for(int j=0;j<i;++j)
			for(int k=1;k<=i;++k)
				for(int p=1;p<=i-k+1;++p)
					for(int q=0;q<=min(p-1,j);++q)
						g[i][j][k]=(g[i][j][k]+f[p][q]*p%mod*c[i-1][p]%mod*g[i-p][j-q][k-1]%mod)%mod;
	}
	ll ans=0;
	for(int i=0;i<=min(m,n);++i)
		ans=(ans+f[n][i])%mod;
	printf("%lld",ans);
}
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
	#endif
	init();
	return 0;
}
posted @ 2024-06-19 11:29  SLS-wwppcc  阅读(34)  评论(0编辑  收藏  举报