题目链接:戳这里
2873: 光之大陆
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 70 Solved: 33
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
Sample Output
HINT
100%测试点保证 n <= 200, m <= 1000000
先简化一下题意:一个n个节点的无向图,要求联通且只存在简单环,没有两环共点,求有多少种连边方式。
将问题分为两步考虑:(1)n个节点构成k个简单环的方案数。(2)构成的k个环缩点后构成树的方案数。
第二个问题看起来较好解决。
先普及一下Purfer编码:Purfer编码是树的一种编码形式,一个Purfer编码对应唯一的一种树的形态。
编码:每次把度数为1的标号最小的点删掉,输出与它相连的点,直到只剩2个点。
还原:把prufer编码中所有节点编号出现的次数加到每个点的度数里面去(初始度数为1),每次取出度数为1的最小的点和当前purfer所在那一位编号相连,并将两个点度数-1.执行n-2次。最后把剩下的两个度数为1的点相连。
显然一个Purfer编码对应唯一的一颗树的形态。
举个栗子:
我们先把度数为1且编号最小的节点删掉,显然赢先一次删掉2,3,4,即图中的绿色节点,并在Purfer编码中加入其与父节点的连边。
所以在删掉2,3,4后,Purfer编码为[1,1,1]。
此时1号节点度数为1,且编号最小,所以应删去1号节点,接着删去6,7,8,号节点,即图中的红色节点。
在删掉1,6,7,8,后,Purfer编码变为[1,1,1,5,5,5,5]。
此时只剩下两个点,编码结束。
可以看出,Purfer编码唯一对应一棵树的形态。
同时我们能够推出结论:n个节点能够生成n^(n-2)个Purfer编码,即有n^(n-2)棵不同的生成树。
那么问题2就解决了。
现在来看问题1:我们需要知道n个点形成k个环的方案数。
定义dp[i][k]表示n个点形成k个环的方案数,仅考虑最后一个环的大小,假设为m,我们可以推出转移方程为dp[i][k]=∑dp[i-m][k-1]*C(m-1,n-1)*(m-1)!/2*m。
因为在n个点中选m个点的方案数为C(m-1,n-1),而m个点成环的方案为圆排列/2,m个点成的环向外连一条出边的方案数为m,证毕。
那么问题得解,枚举k,那么ans=∑dp[n][i]*i^(i-2)。
但有个细节:因为两个点不能成环,一个点(缩点后)可以算作环但没有出边,因此在计算中要忽略两个点的情况,单独算一个点(缩点后)的情况。
复杂度O(n^3)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int read()
{
char c;int sum=0,f=1;c=getchar();
while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
int n,m;
int dp[205][205],s[205];
int c[205][205];
int main()
{
n=read();m=read();
for(int i=0;i<=n;i++)
c[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%m;
s[1]=1;s[3]=3;
for(int i=4;i<=n;i++) s[i]=(s[i-1]*i)%m;
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=i;k++)
dp[i][j]=(dp[i][j]+((LL)dp[i-k][j-1]*c[i-1][k-1]*s[k])%m)%m;
int ans=1;
for(int i=3;i<=n-1;i++) ans=(ans*i)%m;
int tmp=1;
for(int i=2;i<=n;i++)
{
ans+=(LL)dp[n][i]*tmp%m;ans%=m;
tmp=(tmp*n)%m;
}
printf("%d\n",ans);
return 0;
}