【CF814E】 An unavoidable detour for home(组合数学)(DP)

传送门


题解:

由题目给出的两个限制:

1.从1到所有点的最短路唯一
2.1到 i i i的距离不小于1到 i + 1 i+1 i+1的距离

我们可以很显然地发现图可以这样生成:

  1. BFS树就是最短路树,且每个点向深度浅的边的连线只有一条
  2. 同层的点的编号连续,且非树边只可能是同层的点之间的连边。

f [ i ] [ j ] f[i][j] f[i][j]表示考虑前 i i i个点的时候,最后 j j j个点和 i i i点在同一层时候的方案数。

g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]表示这一层有 i i i个点,上一层度数为 2 2 2的点有 j j j个,度数为 3 3 3的点有 k k k个,两层之间连边加上上一层层内连边的方案数,注意这里没有考虑当前层的点之间的连边,因为在考虑下一层的时候当前层的点有可能需要向下连边。

那么很显然,根据定义,最终答案为 ∑ i = 1 n − 1 f [ n ] [ i ] ∗ g [ 0 ] [ c 0 ] [ c 1 ] \sum\limits_{i=1}^{n-1}f[n][i]*g[0][c0][c1] i=1n1f[n][i]g[0][c0][c1],其中 c 0 , c 1 c0,c1 c0,c1分别表示最后 i i i个点中度数为 2 2 2 3 3 3的点的个数。

同时对于 f f f,我们通过枚举上一层有多少个点,有一个可以 O ( n 3 ) O(n^3) O(n3)求解的式子:

f [ i ] [ j ] = ∑ k = 1 i − j − 1 f [ i − j ] [ k ] ∗ g [ j ] [ c 0 ] [ c 1 ] f[i][j]=\sum_{k=1}^{i-j-1}f[i-j][k]*g[j][c0][c1] f[i][j]=k=1ij1f[ij][k]g[j][c0][c1]

这里的 c 0 , c 1 c0,c1 c0,c1是从 i i i倒着数 k k k个的度数计数。

于是我们现在需要考虑的是 g g g的求解。

实际上 g g g也有一个 O ( n 3 ) O(n^3) O(n3)的求解方式。

显然 g [ 0 ] [ 0 ] [ 0 ] = 1 g[0][0][0]=1 g[0][0][0]=1

然后,考虑 g [ 0 ] [ 0 ] [ k ] g[0][0][k] g[0][0][k]怎么算。由于现在只有度数为 3 3 3的点,不需要向下连出儿子边,每个点去掉向上的父亲边之后度数为 2 2 2。所以同层连边之后剩下的是若干个环,需要考虑旋转和翻折同构,实际上就是项链数,记长度为 i i i的非同构项链总数为 a i a_i ai。但是由于不允许重边,需要去掉长度为 2 2 2的环。

枚举这个点所在的环的长度,我们有:

g [ 0 ] [ 0 ] [ k ] = ∑ l = 3 k g [ 0 ] [ 0 ] [ k − l ] ⋅ ( k − 1 l − 1 ) ⋅ a l g[0][0][k]=\sum_{l=3}^{k}g[0][0][k-l]\cdot{k-1\choose l-1}\cdot a_l g[0][0][k]=l=3kg[0][0][kl](l1k1)al

考虑算 g [ 0 ] [ j ] [ k ] g[0][j][k] g[0][j][k]。现在,由于连了父亲边了,所有度数为 2 2 2的点还有一条自由边,度数为 3 3 3的点还有两条自由边,我们可以假设最后新加入的点度数为 2 2 2(其实 3 3 3也可以,但是转移情况有三种,考虑度数为 2 2 2的话只有两种),考虑这个加入的点有一条自由边,现在强制给这条边找到另一端。

如果从度数为 2 2 2的点中选择的话方案数显然是 j − 1 j-1 j1,转化成子问题 g [ 0 ] [ j − 2 ] [ k ] g[0][j-2][k] g[0][j2][k]
如果从度数为 3 3 3的点中选择的话,这个点的自由边就只剩下一条,相当于度数为 2 2 2的点,方案数为 k k k,转化为子问题 g [ 0 ] [ j ] [ k − 1 ] g[0][j][k-1] g[0][j][k1]

所以我们得到第二个转移式:

g [ 0 ] [ j ] [ k ] = g [ 0 ] [ j − 2 ] [ k ] ⋅ ( j − 1 ) + g [ 0 ] [ j ] [ k − 1 ] ⋅ k g[0][j][k]=g[0][j-2][k]\cdot(j-1)+g[0][j][k-1]\cdot k g[0][j][k]=g[0][j2][k](j1)+g[0][j][k1]k

现在考虑求 g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]。其实这里还挺简单的,我们考虑在上一层中选择一个点作为他的父亲,那么他的父亲就要少一条自由边,直接考虑一下选择度数为 2 2 2还是 3 3 3,随便做就行了,则有如下转移:

g [ i ] [ j ] [ k ] = g [ i − 1 ] [ j − 1 ] [ k ] ⋅ j + g [ i − 1 ] [ j + 1 ] [ k − 1 ] ⋅ k g[i][j][k]=g[i-1][j-1][k]\cdot j+g[i-1][j+1][k-1]\cdot k g[i][j][k]=g[i1][j1][k]j+g[i1][j+1][k1]k

总复杂度 O ( n 3 ) O(n^3) O(n3)所以为什么才给n=50


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;}
inline int dec(int a,int b){return (a-=b)<0?a+mod:a;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline bool Inc(int &a,int b){return (a+=b)>=mod&&(a-=mod);}
inline bool Dec(int &a,int b){return (a-=b)<0&&(a+=mod);}

cs int N=55;
int n,ans;
int g[N][N][N],f[N][N],d[N];
int c[N][N],a[N];

inline void init(){
	c[0][0]=1;
	for(int re i=1;i<=n;++i){
		c[i][0]=c[i][i]=1;
		for(int re j=1;j<i;++j)c[i][j]=add(c[i-1][j],c[i-1][j-1]);
	}
	a[2]=a[3]=1;
	for(int re i=4;i<=n;++i)a[i]=mul(a[i-1],i-1);
	g[0][0][0]=1;
	for(int re k=1;k<=n;++k)
	for(int re l=3;l<=k;++l)
	Inc(g[0][0][k],mul(g[0][0][k-l],mul(c[k-1][l-1],a[l])));
	for(int re j=1;j<=n;++j)
	for(int re k=0,lk=n-j;k<=lk;++k)
	(j>=2)&&Inc(g[0][j][k],mul(g[0][j-2][k],j-1)),
	(k>=1)&&Inc(g[0][j][k],mul(g[0][j][k-1],k));
	for(int re i=1;i<=n;++i)
	for(int re j=0,lj=n-i;j<=lj;++j)
	for(int re k=0,lk=lj-j;k<=lk;++k)
	j&&Inc(g[i][j][k],mul(g[i-1][j-1][k],j)),
	k&&Inc(g[i][j][k],mul(g[i-1][j+1][k-1],k));
}

signed main(){
	scanf("%d",&n);
	for(int re i=1;i<=n;++i)scanf("%d",d+i);
	init();
	f[d[1]+1][d[1]]=1;
	for(int re i=d[1]+2;i<=n;++i)
	for(int re j=1,lj=i-d[1]-1;j<=lj;++j)
	for(int re k=1,lk=i-j,c0=0,c1=0;k<lk;++k){
		++(d[i-j-k+1]==2?c0:c1);
		Inc(f[i][j],mul(f[i-j][k],g[j][c0][c1]));
	}
	for(int re i=1,c0=0,c1=0;i<n;++i){
		++(d[n-i+1]==2?c0:c1);
		Inc(ans,mul(f[n][i],g[0][c0][c1]));
	}
	cout<<ans<<"\n";
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值