CodeForces 814E(bfs序DP)

题意:给出 n 个点,和每个点的度让你构造出一张无向图满足以下两条性质:

1.点 1 到点 i 仅有唯一一条最短路  

2.点 1 到点 i 的最短路长度大于等于点 1 到点 i 1 的最短路长度

求能构成满足条件的无向图的个数?

首先我们可以根据上面的两点推出一些结论:

1.根据第一条性质显然这张图是由一棵树加上一些平边构成的(连接深度相同两点的边) 

2.根据第二条性质显然 i 所在的点一定在点 i 1 的同层或下一层  

由此可知,对于两层之间的方案数转移,我们只需要考虑这一层有几个点,上一层有几个度为 2 的点,有几个度为 3 的点。

假设 g [ i ] [ j ] [ k ] 表示当前层有 i 个点,上一层有 j 个度为 2 的点, k 个度为 3 的点的方案数。  

开始考虑这个转移:  
i = 0 , j = 0 , k = 0   

g [ 0 ] [ 0 ] [ 0 ] = 1 ,相当于只有不取一种方案  

i = 0 , j = 0 , k > 0

g [ 0 ] [ 0 ] [ k ] = l = 2 l < k ( g [ 0 ] [ 0 ] [ k l 1 ] C k 1 l ( l ) ! 2 )
每个点度都为 2 所以一定是若干个项链,为了避免重复计算,我们假设项链里一定包含最后一个点,所以方案数为——将最后一个点拿出来与之前任选两个、三个……k-1个点分别构成项链的种类数之和,其中项链数表示选 i 个点构成排列,首尾成环且翻转算重复的方案数, f ( x ) = ( x 1 ) ! 2 ,这里l+1取大于二的原因是因为题目中说两个点之间不能有两条边,所以两个点组成的项链不符合题意。

i = 0 , j > 0 , k > 0

g [ 0 ] [ j ] [ k ] = g [ 0 ] [ j 2 ] [ k ] ( j 1 ) + g [ 0 ] [ j ] [ k 1 ] k
同样是连接最后一个点使其度数为 2
第一种情况表示在 j 1 个可能变成 2 的点里取一个和最后的点连成两个度数为2的点
第二种情况表示在 k 个可能变成 3 的点里取一个和最后的点连接,之前二度的点变成三度,最后一个点变成二度,所以转移时 j 不变, k k 1 转移

i > 0 , j > 0 , k > 0

g [ i ] [ j ] [ k ] = g [ i 1 ] [ j 1 ] [ k ] j + g [ i 1 ] [ j + 1 ] [ k 1 ] k
第一种情况表示上一行选出一个可能变成 2 的点,和当前行最后一个点相连,显然上一行只有一个数变成了二度,所以从 j 1 转移
第二种情况为上一行选出一个可能变成 3 的点,和当前行最后一个点相连,显然上一行原来有一个二度的点变成了三度,所以 j j + 1 转移, k k 1 转移

好的,我们思考完了 g [ i ] [ j ] [ k ] 的转移,开始引入下一个数组

定义 f [ i ] [ j ] 表示当前点是第 i 个,之前的 i 1 i 2 ……等 j 1 个点与 i 在同一层的方案数

f [ i ] [ j ] 的转移方程还是好求的: f [ i ] [ j ] = k = 1 k <= i j f [ i j ] [ k ] g [ j ] [ n ] [ m ]
其中 n m 分别表示这 k 个数中有 n 个度为 2 的点, m 个度为 3 的点,这个方程就不讲解了,可以根据之前的定义自己感受一下。

那么答案就出来了

a n s = j = 1 j < n f [ n ] [ j ] g [ 0 ] [ n ] [ m ]
其中 n m 分别表示这 j 个数中有 n 个度为 2 的点, m 个度为 3 的点

代码如下:

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mod 1000000007
using namespace std;

int n,d[60];
long long a[60],g[60][60][60],f[60][60],c[60][60];

int main()
{
    a[0]=a[1]=0;
    a[2]=a[3]=1;
    for(int i=4;i<=50;i++)
    {
        a[i]=a[i-1]*(i-1)%mod;
    }
    for(int i=0;i<=50;i++)
    {
        c[i][i]=c[i][0]=1;
    }
    for(int i=1;i<=50;i++)
    {
        for(int j=1;j<i;j++)
        {
            c[i][j]=c[i-1][j-1]+c[i-1][j];
            c[i][j]%=mod;
        }
    }
    g[0][0][0]=1;
    for(int j=0;j<=50;j++)
    {
        for(int k=0;k<=50-j;k++)
        {
            if(j==0&&k>0)
            {
                for(int l=2;l<k;l++)
                {
                    g[0][j][k]+=(g[0][j][k-l-1]*c[k-1][l])%mod*a[l+1]%mod;
                    g[0][j][k]%=mod;
                }
            }
            else
            {
                if(j>=2) g[0][j][k]+=(g[0][j-2][k]*(j-1))%mod,g[0][j][k]%=mod;
            if(k>=1) g[0][j][k]+=(g[0][j][k-1]*k)%mod,g[0][j][k]%=mod;
            }

        }
    }
    for(int i=1;i<=50;i++)
    {
        for(int j=0;j<=50-i;j++)
        {
            for(int k=0;k<=50-i-j;k++)
            {
                if(j>=1) g[i][j][k]+=(g[i-1][j-1][k]*j)%mod,g[i][j][k]%=mod;
                if(k>=1) g[i][j][k]+=(g[i-1][j+1][k-1]*k)%mod,g[i][j][k]%=mod;
            }
        }
    }
    int c1,c2;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&d[i]);
    f[d[1]+1][d[1]]=1;
    for(int i=d[1]+2;i<=n;i++)
    {
        for(int j=1;j<=i-d[1]-1;j++)
        {
            c1=c2=0;
            for(int k=1;k<=i-j;k++)
            {
                if(d[i-j-k+1]==2) c1++;
                else c2++;
                f[i][j]+=f[i-j][k]*g[j][c1][c2]%mod;
                f[i][j]%=mod;
            }
        }
    }
    c1=c2=0;
    long long ans=0;
    for(int j=1;j<n;j++)
    {
        if(d[n-j+1]==2)c1++;
        else c2++;
        ans+=f[n][j]*g[0][c1][c2]%mod;
        ans%=mod;
    }
    printf("%lld\n",ans);
}

感谢liu_cheng_ao在loj上发表的题解以及残念在csdn上发表的题解对我的启发

猜你喜欢

转载自blog.csdn.net/qq_21829533/article/details/82391687
今日推荐