codeforces814E. An unavoidable detour for home(dp套dp)

博客探讨了一道图论题目,其中要求构造一张特殊的分层图,确保从首都1号点出发的最短路径是唯一的。题目强调较小编号的点到首都的最短路径不超过较大编号的点,并给出每点的度数限制。解决方案涉及到动态规划(DP)技巧,通过状态转移方程来计算不同层数和节点分布的方案数,最终模10^9+7。博客中还提到了如何预处理状态并进行边界情况讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门
题意:
有一张图,其中1号点是首都。已知编号更小的点到首都的最短距离一定不会超过编号更大的点。(距离定义为经过的边数) 要求每个点到首都的最短路径唯一。 且图上不存在重边与自环。 给定所有点的度数(2或3),求方案数mod 10^9+7。 n<=50
思路:
看到限制就应该有大概思路:一个合法的图应该是分层的,且每一层中的数编号连续。
于是不难设置状态 f i , j f_{i,j} fi,j表示前 i i i个点最后一层有 j j j个点的方案数。
考虑枚举上一层的节点数 k k k转移: f i , j = f i − j , k ∗ ? ? ? f_{i,j}=f_{i-j,k}*??? fi,j=fij,k???
于是将问题转化成了求 ? ? ? ??? ???
这个 ? ? ? ??? ???可以 d p dp dp出来。
定义状态 g i , j , k g_{i,j,k} gi,j,k表示当前层有 i i i个点,上一层有 j j j个度数为 2 2 2的和 k k k个度数为 1 1 1的,且当前层每个点恰和上一层某个点相连的方案数。
这个 ? ? ? ??? ???就等于 g j , c n t _ d u _ 2 i − j − k + 1 , i − j , c n t _ d u _ 3 i − j − k + 1 , i − j g_{j,cnt\_du\_2_{i-j-k+1,i-j},cnt\_du\_3_{i-j-k+1,i-j}} gj,cnt_du_2ijk+1,ij,cnt_du_3ijk+1,ij
至于 g g g的预处理可以分情况讨论。
详见代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int mod=1e9+7;
typedef long long ll;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline void Add(int&a,const int&b){a=a+b>=mod?a+b-mod:a+b;}
inline void Dec(int&a,const int&b){a=a>=b?a-b:a-b+mod;}
inline void Mul(int&a,const int&b){a=(ll)a*b%mod;}
const int N=55;
int n,a[N],f[N][N],g[N][N][N],c1[N][N],c2[N][N],C[N][N],coe[N],ans=0;
inline void init(){
	for(ri i=0;i<=n;++i){
		C[i][0]=1;
		for(ri j=1;j<=i;++j)C[i][j]=add(C[i-1][j],C[i-1][j-1]);
	}
	coe[3]=1;
	for(ri i=4;i<=n;++i)coe[i]=mul(coe[i-1],i-1);
}
int main(){
	n=read();
	init();
	for(ri i=1;i<=n;++i)a[i]=read();
	for(ri l=1;l<=n;++l)for(ri r=l;r<=n;++r){
		c1[l][r]=c1[l][r-1]+(a[r]==2);
		c2[l][r]=c2[l][r-1]+(a[r]==3);
	}
	g[0][0][0]=1;
	for(ri i=3;i<=n;++i)for(ri j=3;j<=i;++j)Add(g[0][0][i],mul(coe[j],mul(C[i-1][j-1],g[0][0][i-j])));
	for(ri i=1;i<=n;++i)for(ri j=0;j<=n;++j)g[0][i][j]=add(i>=2?mul(i-1,g[0][i-2][j]):0,j?mul(j,g[0][i][j-1]):0);
	for(ri i=1;i<=n;++i)for(ri j=0;j<=n;++j)for(ri k=0;k<=n;++k)g[i][j][k]=add(j?mul(j,g[i-1][j-1][k]):0,k?mul(k,g[i-1][j+1][k-1]):0);
	f[a[1]+1][a[1]]=1;
	for(ri i=a[1]+2;i<=n;++i)for(ri j=1;j+a[1]+1<=i;++j)for(ri k=1;k<=i-j;++k)
	Add(f[i][j],mul(f[i-j][k],g[j][c1[i-j-k+1][i-j]][c2[i-j-k+1][i-j]]));
	for(ri i=1;i<=n;++i)Add(ans,mul(f[n][i],g[0][c1[n-i+1][n]][c2[n-i+1][n]]));
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值