【NOI.ac#31】MST

题目链接

题目描述

小 D 最近学习了最小生成树的有关知识。为了更好地学习求最小生成树的流程,他造了一张 n 个点的带权无向完全图(即任意两个不同的点之间均有且仅有一条无向边的图),并求出了这个图的最小生成树。

为了简单起见,小 D 造的无向图的边权为 [1,n(n−1)/2][1,n(n−1)/2] 之间的整数,且任意两条边的边权均不一样。

若干天后,小 D 突然发现自己不会求最小生成树了。于是他找出了当时求出的最小生成树,但是原图却怎么也找不到了。于是小 D 想要求出,有多少种可能的原图。但是小 D 连最小生成树都不会求了,自然也不会这个问题。请你帮帮他。

形式化地,你会得到 n−1 个递增的正整数 a1,a2,⋯,an−1,依次表示最小生成树上的边的边权。你要求出,有多少张 n 个点的带权无向完全图,满足:
每条边的边权为 [1,n(n−1)/2][1,n(n−1)/2] 之间的整数;
任意两条不同的边的边权也不同;
至少存在一种最小生成树,满足树上的边权按照从小到大的顺序排列即为 a1,a2,⋯,an−1(事实上,可以证明任意一张无向图的任意两棵最小生成树上的边权集合相同)。

因为答案可能很大,所以你只要求出答案对 109+7(一个质数)取模的结果即可。

Sol

DP

暴力dp的做法:
用最小表示法记录每个点在的联通块 , 然后把这个数组 哈希 或者 map 掉

要加入一条树边时,先要加入之前的非树边 , 而不能改变树的形态 , 因此可以直接暴力算出有多少个不会使得连通性改变的边 排列数计算方案就可以了

加入树边则暴力枚举合并哪两个块然后转移

考虑优化:
其实有一些状态的转移是一样的 , 这样的情况发生在两个状态的无序处理后是相同的情况下

而其实这个就是 一个 n 的无序划分 , 所以考虑直接用整数的拆分数来表示状态 , 即使n是40 ,拆分数也不足 40000

所以暴搜出拆分数,然后 哈希 或 map 掉 , 和上面一样的转移就好了

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<set>
#include<map>
#define Set(a,b) memset(a,b,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(a))
using namespace std;
int n;
const int mod=1e9+7;
const int N=41;
const int UP=40000;
int a[N];
int S=0;
const int MO=1e6+3;
const int base=131;
struct fen{
	int num[41];
	inline void clear(){num[0]=0;}
	inline int size(){return num[0];}
	inline void push(int x){num[++num[0]]=x;}
	inline void pop(){--num[0];}
	inline void trans(){
		int st[41];Set(st,0);
		for(register int i=1;i<=num[0];++i) ++st[num[i]];
		Copy(num,st);
		return;
	}
	inline void IN(int *st){for(register int i=0;i<=40;++i) st[i]=num[i];return;}
	inline void out(){for(register int i=1;i<=n;++i) printf("%d ",num[i]);return (void)puts("");}
	inline bool operator <(fen B)const{
		for(register int i=0;i<=40;++i) if(num[i]!=B.num[i]) return num[i]<B.num[i];
		return 0;
	}
	inline bool operator ==(fen B)const{
		for(register int i=0;i<=40;++i) if(num[i]!=B.num[i]) return 0;
		return 1;
	}
}Z[UP];
map<fen,int> M;
fen P;
void dfs(int pre,int now)
{
	if(!now) {Z[++S]=P;return;}
	for(register int i=pre;i<=now;++i) P.push(i),dfs(i,now-i),P.pop();
	return;
}
int dp[2][UP];
const int MAXN=N*N;
int fac[MAXN],inv[MAXN],finv[MAXN];
queue<int> Q;
int st[UP];
bool in[UP];
int main()
{
	cin>>n;
	for(register int i=1;i<n;++i) cin>>a[i];
	sort(a+1,a+n); dfs(1,n);
	register int cur=0;fac[0]=1;finv[0]=finv[1]=inv[1]=1;
	for(register int i=2;i<MAXN;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(register int i=1;i<MAXN;++i) fac[i]=1ll*fac[i-1]*i%mod,finv[i]=1ll*finv[i-1]*inv[i]%mod;
	for(register int i=1;i<=S;++i) {Z[i].trans();M[Z[i]]=i;}
	dp[0][1]=1;Q.push(1);
	for(register int i=1;i<n;++i) {
		cur^=1;Set(dp[cur],0);
		register int num=a[i]-a[i-1]-1;
		while(!Q.empty()) st[++st[0]]=Q.front(),Q.pop(),in[st[st[0]]]=0;
		while(st[0]){
			register int u=st[st[0]--];
			register int sum=1;
			if(num){
				sum=0;
				for(register int k=1;k<=n;++k) {register int K=k*(k-1)/2;sum+=Z[u].num[k]*K;}
				sum-=a[i-1];
				if(sum<num) continue;
				sum=1ll*fac[sum]*finv[sum-num]%mod;
			}
			sum=1ll*sum*dp[cur^1][u]%mod;
			if(!sum) continue;
			fen P=Z[u];
			for(register int p=1;p<=n;++p){
				if(!P.num[p]) continue;
				register int K=0;
				
				if(P.num[p]>=2){
					K=(P.num[p]*(P.num[p]-1)/2)*p*p;
					P.num[p]-=2;++P.num[p<<1];
					register int id=M[P];
					(dp[cur][id]+=1ll*K*sum%mod)%=mod;
					P.num[p]+=2;--P.num[p<<1];
					if(!in[id]) in[id]=1,Q.push(id);
				}
				
				for(register int q=p+1;q<=n;++q){
					if(!P.num[q]) continue;
					K=P.num[p]*P.num[q]*p*q;
					--P.num[p];--P.num[q];++P.num[p+q];
					register int id=M[P];
					(dp[cur][id]+=1ll*K*sum%mod)%=mod;
					++P.num[p];++P.num[q];--P.num[p+q];
					if(!in[id]) in[id]=1,Q.push(id);
				}
			}
		}
	}
	register int tot=n*(n-1)/2;
	dp[cur][S]=1ll*dp[cur][S]*fac[tot-a[n-1]]%mod;
	printf("%d\n",dp[cur][S]);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/82768959
MST