【XSY1295】calc n個點n條邊無向連通圖計數 prufer序列
題目大意
求\(n\)個點\(n\)條邊的無向連通圖的個數
\(n\leq 5000\)
題解
顯然是一個環上有很多外向樹。
首先有一個東西:\(n\)個點選\(k\)個點作為樹的根的生成森林個數為:
\[
\binom{n}{k}\times n^{n-k-1}\times k
\]
前面\(\binom{n}{k}\)是這些根的選編號的方案數,後面是prufer序列得到的:前面\(n-k-1\)個數可以是\(1\)~\(n\),第\(n-k\)個數是\(1\)~\(k\)。
我的理解是:每個序列決定了一部分點作為"葉子節點",剩下的每個點按順序選一個編號最小的"葉子節點"作為這個點的兒子(選編號最小的是因為1.如果一個點可以選多個兒子就不會重復計數;2.兩個數的先後順序不同,那麽選的兒子也不同,會讓先後順序成為影響因素),然後如果這個點不能再選兒子,那麽這個點就會成為"葉子節點"。選了\(n-k-1\)
最後\(k\)個點的環的排列方式有\(\frac{(k-1)!}{2}\)。你可以選編號最小的點為"根",剩下\(k-1\)個點每次選一個點連向上一個點,最後一個點再連向第一個點。因為環可以翻轉,所以方案數要除以\(2\)。你也可以認為是先生成一個排列,然後旋轉這個環(除以\(k\)),然後翻轉這個環(除以\(2\))。
最終的式子是
\[
\begin{align}
&~~~~~\sum_{k=3}^{n}\binom{n}{k}\times n^{n-k-1}\times k\times \frac{(k-1)!}{2}\&=\sum_{k=3}^{n}\frac{n!\times n^{n-k-1}\times k\times (k-1)!}{k!\times (n-k)!\times 2}\&=\sum_{k=3}^{n}\frac{n!n^{n-k-1}}{2(n-k)!}
\end{align}
\]
寫個高精度什麽的亂搞一下就可以了。
時間復雜度:\(O(n^2)\)
代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int p=10000;
struct bign
{
int a[5010];
bign()
{
memset(a,0 ,sizeof a);
}
int &operator [](int x)
{
return a[x];
}
bign &operator *=(int v)
{
int i,s,g=0;
bign &a=*this;
for(i=1;i<=5000;i++)
{
s=g+a[i]*v;
g=s/p;
a[i]=s%p;
}
return a;
}
bign &operator /=(int v)
{
int i,s,g=0;
bign &a=*this;
for(i=5000;i>=1;i--)
{
s=g*p+a[i];
a[i]=s/v;
g=s%v;
}
g=0;
for(i=1;i<=5000;i++)
{
s=g+a[i];
a[i]=s%p;
g=s/p;
}
return a;
}
bign &operator +=(bign &b)
{
int i,s,g=0;
bign &a=*this;
for(i=1;i<=5000;i++)
{
s=a[i]+b[i]+g;
a[i]=s%p;
g=s/p;
}
return a;
}
};
bign a,ans;
int main()
{
int n;
scanf("%d",&n);
int i;
a[1]=1;
for(i=2;i<=n-1;i++)
a*=i;
ans+=a;
for(i=n-1;i>=3;i--)
{
a*=n;
a/=n-i;
ans+=a;
}
ans/=2;
for(i=5000;!ans[i];i--);
printf("%d",ans[i]);
for(i--;i;i--)
printf("%04d",ans[i]);
printf("\n");
return 0;
}
【XSY1295】calc n個點n條邊無向連通圖計數 prufer序列