题目描述
小 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)