NOI.AC #31. MST
好像又是神仙dp。。。。gan了一早上
首先这是个计数类问题,上DP,
对于一个最小生成树,按照kruskal是一个个联通块,枚举边小到大合成的
假如当前边是树边,那么转移应该还是枚举两个块然后合并
假如不是树边那么就在所有联通块所有非树边中任选一条
两个相邻树边之间的非树边方案应该是P(所有联通块总边数-(当前枚举到那条边-1),r-l-1)
然而按照我现在的智商还是不会捉
%了题解发现一个非常强大的性质,就是对于一个整数的无序拆分很小,40只有37338
设f[zt],其中zt表示一个状态,由一些联通块的大小组成,总和为n
这样可以爆搜一波把所有无序拆分也就是状态弄出来,并给一个新编号
转移就是枚举两个联通块然后合并
若第i,j个合并
(新状态的方案)+=(这个状态的方案)*(两条树边之间其他边选择的方案)*(第i个联通块的大小)*(第j个联通块的大小)
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<map> using namespace std; typedef long long LL; const LL mod=1e9+7; int n; struct zhuangtai { int u[50]; friend bool operator >(zhuangtai z1,zhuangtai z2) { if(z1.u[0]==z2.u[0]) { for(int i=1;i<=z1.u[0];i++) if(z1.u[i]!=z2.u[i])return z1.u[i]>z2.u[i]; } return z1.u[0]>z2.u[0]; } friend bool operator <(zhuangtai z1,zhuangtai z2) { if(z1.u[0]==z2.u[0]) { for(int i=1;i<=z1.u[0];i++) if(z1.u[i]!=z2.u[i])return z1.u[i]<z2.u[i]; } return z1.u[0]<z2.u[0]; } int getsum() { int ret=0; for(int i=1;i<=u[0];i++) ret=(ret+(u[i]*(u[i]-1)/2)%mod)%mod; return ret; } }mp[41000];int z,g[50]; bool cmp(zhuangtai z1,zhuangtai z2){return z1>z2;} map<zhuangtai,int>id;//通过状态找编号 void dfs(int d,int last)//预处理拆分n的方案 { if(d==n) { z++; for(int i=0;i<=g[0];i++)mp[z].u[i]=g[i]; return ; } for(int i=last;i+d<=n;i++) { g[++g[0]]=i; dfs(i+d,i); g[g[0]--]=0; } } LL quick_pow(LL A,LL p) { LL ret=1; while(p!=0) { if(p%2==1)ret=ret*A%mod; A=A*A%mod;p/=2; } return ret; } LL fac[110000],fac_inv[110000]; LL getP(int n,int m){return fac[n]*fac_inv[n-m]%mod;} zhuangtai t;int h[50]; int getnzt(int zt,int x,int y) { memcpy(h,mp[zt].u,sizeof(h)); int d=h[x]+h[y]; memset(t.u,0,sizeof(t.u)); for(int i=1;i<=h[0];i++) { if(i!=x&&i!=y) { if(d!=-1&&h[i]>d)t.u[++t.u[0]]=d,d=-1; t.u[++t.u[0]]=h[i]; } } if(d!=-1)t.u[++t.u[0]]=d; return id[t]; } int a[50];LL f[41000]; int main() { fac[0]=1,fac_inv[0]=1; for(int i=1;i<=100000;i++) fac[i]=fac[i-1]*i%mod,fac_inv[i]=quick_pow(fac[i],mod-2); scanf("%d",&n); for(int i=1;i<n;i++)scanf("%d",&a[i]); z=0;dfs(0,1); sort(mp+1,mp+z+1,cmp); for(int i=1;i<=z;i++)id[mp[i]]=i; memset(f,0,sizeof(f));f[1]=1; for(int zt=1;zt<=z;zt++) if(f[zt]>0) { int e=n-mp[zt].u[0]+1;//轮到第几条边用来合并 LL P=getP(mp[zt].getsum()-(a[e-1]),a[e]-a[e-1]-1);//两条树边中间其他边选择的方案数 for(int i=1;i<=mp[zt].u[0];i++) for(int j=i+1;j<=mp[zt].u[0];j++) { int nzt=getnzt(zt,i,j); f[nzt]=(f[nzt]+f[zt]*P%mod*mp[zt].u[i]%mod*mp[zt].u[j]%mod)%mod; } } int rst=n*(n-1)/2-a[n-1]; printf("%lld\n",f[z]*getP(rst,rst)%mod); return 0; }
pain and happy in the cruel world.