2418. 光之大陆
题目链接
2418. 光之大陆
在光之大陆的土地上,各种势力盘根错节。
来自光之峡谷的精灵,来自黑暗森林的亡灵,来自古老东方的人类共同生活在一起。
善于打造装置的矮人,善于发明的侏儒,隐匿于山林的巨人也坚守着属于自己的领土。
这些种族之间关系错综复杂,构成了极其庞大的关系网络。
大魔法师小 \(P\) 想要研究其中的种族关系。
两个物种之间可以是盟友,也可以不是盟友,如果 \(a_1,a_2..a_n\) 满足 \(a_i\) 和 \(a_{i+1}\) 是盟友,且 \(a_n\) 和 \(a_1\) 是盟友,则他们构成了一个联盟。
由于光之大陆正处于微妙的和平之中。所以一个合理的物种关系应满足如下条件:
- 对于任意两个物种 \(A,B\),都存在一个序列 \(A,a_1,a_2..a_n,B\),使得任意相邻两个种族是盟友(注意 \(A,B\) 不一定是盟友)。
- 对于任意两个联盟 \(S_a,S_b\),都不存在一个物种既参加了联盟 \(S_a\),又参加了联盟 \(S_b\)。
小 \(P\) 想知道,大陆上的 \(N\) 个种族一共有多少种可能的结盟关系,由于结果可能很大,你只需要输出答案 \(\bmod M\) 的值。
输入格式
一行,两个正整数 \(N,M\)。
输出格式
一个整数,表示方案数 \(\bmod M\) 的值。
数据范围
\(3 \le N \le 200\),
\(1 \le M \le 10^6\)
输入样例:
4 1000000
输出样例:
31
解题思路
prufer编码
问题即给出一个 \(n\) 个点的无向完全图,问有多少个不同的点仙人掌图
将该问题拆解:先求出 \(n\) 个点分成 \(m\) 个环的方案,然后再将这些环组成生成树的方案数,对于第二步,考虑prufer编码,每个点的父亲节点的范围为 \(1\sim n\),且由于是完全图,不难发现,对于任意一个点的父亲节点,对于 \(1\sim n\) 的任何一个点作为其父亲节点的概率都是相等的,即prufer编码有 \(n^{m-2}\) 种,即将环组成生成树的方案有 \(n^{m-2}\) 种,再来考虑第一步,考虑 dp
,\(f[i][j]\) 表示前 \(i\) 个点分成 \(j\) 个环的方案数,枚举 \(1\) 号所在环的环数,这样的环有 \(C_{i-1}^{k-1}\) 种,环也有不同的种类,\(k\) 点排列有 \(k!\),假定我们每次都是拿环上最上面的那个点连边,则显然有 \(k\) 种方案,即对应全排列,可以看成旋转操作形成的环是不同的,但是由于是无向图,对于某条边来说会连两次,则这部分的转移方程:\(f[i][j]=\sum_{k=1}^{i-j+1}f[i-k][j-1]\times C_{i-1}^{k-1}\times \frac{k!}{2}\),则答案为 \(\sum_{k=1}^{n}f[n][k]\times n^{k-2}\),观察式子,不难发现 \(k=1\) 时会出问题,\(\color{red}{为什么?}\)对于含 \(n\) 个点的环,这时显然不用向外连边,即这时的总方案应该为 \(\frac{n-1}{2}\)
- 时间复杂度:\(O(n^3)\)
代码
// Problem: 光之大陆
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/2420/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=205;
int n,m,C[N][N],g[N],f[N][N];
void init()
{
C[0][0]=1;
for(int i=0;i<=n;i++)
for(int j=0;j<=i;j++)
{
if(!j)C[i][j]=1;
else
C[i][j]=(C[i-1][j-1]+C[i-1][j])%m;
}
g[1]=1%m,g[3]=3%m;
for(int i=4;i<=n;i++)g[i]=(LL)g[i-1]*i%m;
}
int main()
{
scanf("%d%d",&n,&m);
init();
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=i-j+1;k++)
f[i][j]=(f[i][j]+(LL)f[i-k][j-1]*C[i-1][k-1]*g[k]%m)%m;
int res=g[n-1],p=1;
for(int i=2;i<=n;i++)
{
res=(res+(LL)f[n][i]*p)%m;
p=(LL)p*n%m;
}
printf("%d",res);
return 0;
}