noi.ac #31 MST / 记忆碎片 社论
MST / 记忆碎片
一个 个点的有标号带权无向图 ,边权均为介于 间的整数 . 给定 MST 上所有边的边权,问原图有多少种本质不同方案,答案对 取模 .
两个方案本质不同当且仅当有一条边边权不同 .
.
考虑 Kruskal 求 MST 的过程,我们把树边一条一条加进去,在加下一条之前先把权值在的边都连上,并保证图的连通性不变 .
令 表示添加了 条树边目前连通状态为 的方案数,不难发现 只需要记录一下某个大小的连通块有多少,此外还需要记一下空位数要不然难转移 .
加一条树边的过程就相当于合并两个连通块,我们只需要处理出有哪些本质不同的转移点,这个可以直接暴搜 .
DFS 暴搜就行,不需要 Delov 的高端 BFS,因为我不会 bitset
所以就直接拿数组记录的,然后搜完之后对状态 Hash 一下,用一个 std :: map
记录一下下一个转移点即可 .
转移点处理好了转移过程就分树边和非树边讨论一下转移即可:
- 树边:相当于合并连通块,设目前要合并 ,对应分别有 个,大小为 ,则:
- 非树边:根据基础组合数学可以发现答案就是空位下降幂 .
不同大小的连通块只会有 个,枚举两个连通块复杂度是 的。而需要进行枚举的转移也只有 次,又已经加了一些树边之后,图的连通性状态数可以由整数划分表出,所以复杂度大概是 的,其中 是分拆数, 十分能过啊 .
或者科学一点可以分析一下分拆数渐进式(声明:我不会),可以看一下《Analytic Combinatorics》.
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair<int, int> pii;
const int N = 45, _M_ = 37339, base = 1331, P = 1e9+7;
int n, m, a[N], siz[N], fac[N*N], ifac[N*N], Bx[N], id[N][N], ans;
inline int qpow(int a, int n)
{
int ans = 1;
while (n)
{
if (n & 1) ans = 1ll * ans * a % P;
a = 1ll * a * a % P; n >>= 1;
} return ans;
}
inline void initfac(int n)
{
fac[0] = 1;
for (int i=1; i<=n; i++) fac[i] = 1ll * i * fac[i-1] % P;
ifac[n] = qpow(fac[n], P-2);
for (int i=n-1; i>=0; i--) ifac[i] = 1ll * ifac[i+1] * (i+1) % P;
}
inline int A(int n, int m){return n<m ? 0 : 1ll * fac[n] * ifac[n-m] % P;}
int now, tt[N], B[N], v[N][_M_][N], e[N][_M_], dp[N][_M_];
unordered_map<ull, int> M[N];
void dfs(int b, int st, int lst)
{
if (lst * (now - st + 1) > b) return ;
if (st > now)
{
ull hash = 0;
for (int i=1; i<st; i++) hash = hash * base + B[i];
M[now][hash] = ++tt[now];
for (int i=1; i<st; i++)
{
v[now][tt[now]][i] = B[i];
e[now][tt[now]] += 1ll * B[i] * (B[i]-1) / 2;
}
return ;
}
if (st == now){B[st] = b; dfs(0, st+1, 0); return ;}
for (int i=lst; i<=b; i++){B[st] = i; dfs(b-i, st+1, i);}
}
int main()
{
scanf("%d", &n); initfac(a[1] = n*(n-1)/2);
for (int i=n; i>1; i--) scanf("%d", a+i);
for (int i=1; i<=n; i++){now = i; dfs(n, 1, 1);}
dp[n][1] = 1;
for (int i=n; i>1; i--)
for (int s=1; s<=tt[i]; s++)
{
dp[i][s] = 1ll * dp[i][s] * A(e[i][s]-a[i+1], a[i]-a[i+1]-1) % P;
memset(id, 0, sizeof id);
for (int j=1; j<=i; j++) B[j] = v[i][s][j];
for (int u=1; u<=i; u++)
for (int v=u+1; v<=i; v++)
{
if (!id[B[u]][B[v]])
{
int top = 0;
for (int p=1; p<=i; p++)
if ((p != u) && (p != v)) Bx[++top] = B[p];
Bx[++top] = B[u] + B[v];
for (int p=top-1; p; p--) // a part of insertion sort
if (Bx[p] > Bx[p+1]) swap(Bx[p], Bx[p+1]);
else break;
ull hash = 0;
for (int p=1; p<=top; p++) hash = hash * base + Bx[p];
id[B[u]][B[v]] = M[i-1][hash];
}
(dp[i-1][id[B[u]][B[v]]] += 1ll * dp[i][s] * B[u] % P * B[v] % P) %= P;
}
}
printf("%d\n", dp[1][1] = 1ll * dp[1][1] * A(e[1][1] - a[2], a[1] - a[2] - 1) % P);
return 0;
}
好像目前 hszxoj 里 T1 T4 我都是最优解(时间),谁来写一份高效的代码啊?话说直接贺我代码的都没我跑得快
以下是博客签名,正文无关
本文来自博客园,作者:yspm,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/16600894.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误