给定长 的序列 ,求有多少个 个点的有标号简单无向图(无自环重边)满足:
- 点 的度数为 ;
- 点 到点 有且仅有一条最短路;
- ,点 到点 的最短路长度大于等于点 到点 的;
对 取模。
,。
考虑 bfs,显然图可以分成若干层,每一层点的编号连续且 bfs 树上的横叉边都在同一层内。
那么不难想到设 表示处理完前 个点,最后一层往下连了 条边的方案数。
转移时下一层的区间一定是 ,且区间内去掉当前层的 条边就只剩下一度点和二度点。那么不难想到设 表示一层内有 个 度点和 个二度点,往下一层连了 条边的内部连边方案数。则有:
其中 和 表示 中 和 的个数。
但是发现这样会算重,因为二度点无论连哪边都是一样的。
所以若上一层有 个二度点是两边都往下连的,则转移时需要除掉 。
这个问题是好处理的,只需要把 放进 中即可。
现在来考虑 怎么求,首先显然有 。
不难发现一度点和二度点之间的顺序是没有关系的,所以不妨钦定先放一度点再放二度点。
下文 x 表示一度点,o 表示二度点,- 表示层内连边,+ 表示向下一层的连边
对于没有二度点的情况,考虑最后一个一度点 :
- u+型:;
- x-u型:;
对于有二度点的情况,考虑最后一个二度点 。为了避免算重, 多出来的边直接往下连:
- +u+型:,其中 是转移时的 ;
- x-u+型:;
- -o-u+型:连出去的二度点规约为一个一度点,;
- x-u-x型:;
- x-u-o-型:连出去的二度点规约为一个一度点,;
- -o-u-o-型:两个二度点都规约为一度点,;
时间复杂度 ,空间复杂度 。
代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int S=305,p=1000000007,inv2=(p+1)/2;
int fra[S];
int n,a[S];
int g[S][S][S],f[S][S];
inline void add(int &x,int y)
{
	x+=y;
	if(x>=p) x-=p;
}
inline int qpow(int x,int y)
{
	int res=1;
	for(;y>0;y>>=1,x=1ll*x*x%p) res=y&1?1ll*res*x%p:res;
	return res;
}
int main()
{
	fra[0]=1;
	for(int i=1;i<=S-3;i++) fra[i]=1ll*fra[i-1]*i%p;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	g[0][0][0]=1;
	for(int k=0;k<=n;k++)
	{
		for(int j=0;j<=n&&j+k<=n;j++)
		{
			for(int i=0;i<=n&&i+j+k<=n;i++)
			{
				if(i+j*2<k) continue;
				if(i+j+k==0) continue;
				int &u=g[i][j][k];
				if(j==0)
				{
					// u-
					if(k>=1) add(u,g[i-1][j][k-1]);
					// u-x
					if(i>=2) add(u,1ll*g[i-2][j][k]*(i-1)%p);
				}
				else
				{
					// x-u-x
					if(i>=2) add(u,1ll*g[i-2][j-1][k]*i%p*(i-1)%p*inv2%p);
					// x-u-o- OR -o-u-x
					if(i>=1&&j>=2) add(u,1ll*g[i][j-2][k]*i%p*(j-1)%p);
					// -o-u-o-
					if(j>=3) add(u,1ll*g[i+2][j-3][k]*(j-1)%p*(j-2)%p*inv2%p);
					if(k>=1)
					{
						// x-u- OR -u-x
						if(i>=1) add(u,1ll*g[i-1][j-1][k-1]*i%p);
						// -o-u- OR -u-o-
						if(j>=2) add(u,1ll*g[i+1][j-2][k-1]*(j-1)%p);
						if(k>=2)
						{
							// -u-
							add(u,1ll*g[i][j-1][k-2]*inv2%p);
						}
					}
				}
			}
		}
	}
	f[1][a[1]]=qpow(fra[a[1]],p-2);
	for(int i=1;i<=n-1;i++)
	{
		int c1=0,c2=0;
		for(int j=i+1;j<=n;j++)
		{
			c1+=a[j]==2;
			c2+=a[j]==3;
			for(int k=0;k<=n&&k<=c1+c2*2;k++)
			{
				int pre=1ll*f[i][j-i]*g[c1][c2][k]%p*fra[j-i]%p;
				add(f[j][k],pre);
			}
		}
	}
	printf("%d\n",f[n][0]);
	return 0;
}
