写在前面

  • OI 生涯中 AC 的首道组合数学应用题。

  • 开题 5min 发现规律,写了半下午代码,调了两天,然而甚至没过样例,心态崩了。几天之后重新写了一份代码才 AC。

  • 虽然思维难度不大,但毕竟是联赛题,题目质量还是很高的。涉及到了很多组合数学的基础算法,写完之后感觉学到了很多。感觉这道题是道不错的组合数学入门题。接下来我将会尽量详细地进行讲解。

数据结构

只有根节点的答案有用。任何一个节点在更新完其父亲结点的值后,其本身的任何值将不会再有任何改动或贡献,因此用并查集维护即可,记得路径压缩。

算法思路

解决这道题的关键,在于连接两个节点时答案的更新和与答案相关的值的维护。

设 \(a_i\) 表示以 \(i\) 为根节点的子树的填数方案总数。

假设当前连接的两个节点的编号分别是 \(u,v\)(保证两个节点都是根节点),且本次操作需要从 \(u\) 接到 \(v\) 上去。

比较显而易见的一点是,更新答案时,节点 \(v\) 所处的位置,一定只能填 \(0\)。

维护 \(w_i\) 表示以 \(i\) 为根的树的重量,即节点数。将 \(u\) 连接到 \(v\) 上时,将 \(w_v\) 的值加上 \(w_u\)。

那么填入原来的 \(u\) 子树中的数字就有 \(w_v - 1\) 选 \(w_u\) 种不同的选择方案。剩下的数字填入原来的 \(v\) 子树中。又原本 \(u,v\) 子树中的填数总方案数分别为 \(a_u,a_v\)。那么根据乘法原理,就可以将 \(a_v\) 更新为 \(a_v \times a_u \times \binom{n}{m}\)

组合数求法

  1. 杨辉三角,加法,可以取模。但是这是 P2822 的组合数求法,需要用到二维数组,数组大小开不下,只能过 50% 的数据。

  2. 直接根据组合数计算公式:

\[\binom{n}{m} = \frac{n!}{m!(n - m)!}
\]

直接用计算式来求组合数。预处理出阶乘。等下,模意义下的乘法……要求逆元啊。线性递推求逆元可以参考 P3811 的题解。

这道题的除数是可以大到 \(10^9 + 7\) 的,因此不能直接递推预处理逆元,但是只会用到阶乘的逆元,预处理这个即可。

那么怎么线性递推呢?看这里:(为了更好体现逆元的形式,式子中保证了分数分子上都是 \(1\))

\[\frac{1}{i!} = \frac{1}{i\cdot(i - 1)!} = \frac{1}{i}\cdot\frac{1}{(i - 1)!}
\]
\[\frac{1}{(i - 1)!} = i\cdot\frac{1}{i!}
\]

因此,首先根据费马小定理推论用快速幂算出 \(\frac{1}{n!}\) 随后倒序枚举预处理即可。

Tips

  • 节点编号是从 \(0\) 开始的,记得初始化的时候把 \(a_0,w_0\) 也一并设为 \(1\)。(我卡了几天就是因为这个)
  • 注意输入格式,别忘了强制在线。
  • 处理阶乘的逆元记得要枚举到 \(0\),不然 \(m = 0\) 的时候 \(\binom{n}{m}\) 的值是错的。

Code

代码可读性还是很高的。

#include<bits/stdc++.h>
#define LL long long using namespace std; const int Maxn = 3e5 + 5;
const LL Mod = 1e9 + 7; int n, q, opt, x, y;
int Ans;
LL a[Maxn] = {1};
int w[Maxn] = {1}; /*快速幂*/
inline LL qpow(LL b, LL p)
{
if(p == 0) return 1;
LL x = 1;
for(; p;b *= b, b %= Mod,p >>= 1) if(p & 1) x *= b, x %= Mod;
return x;
} /*并查集*/
int fa[Maxn]; int find(int t)
{
return fa[t] == t ? t : fa[t] = find(fa[t]);
} /*数学*/
LL fac[Maxn] = {1};
LL invf[Maxn]; LL C(int N, int M)
{
return 1ll * fac[N] % Mod * invf[M] % Mod * invf[N - M] % Mod;
} /*预处理*/
void Setup()
{
for(register int i = 1; i <= n; ++i)
{
fa[i] = i;
w[i] = a[i] = 1;
fac[i] = fac[i - 1] * i;
fac[i] %= Mod;
}
invf[n] = qpow(fac[n], Mod - 2);
for(register int i = n - 1; i >= 0; --i)
{
invf[i] = invf[i + 1] * (i + 1);
invf[i] %= Mod;
}
} /*链接 处理答案*/
void line(int u, int v)
{
w[v] += w[u];
a[v] = a[v] * a[u] % Mod * C(w[v] - 1, w[u]) % Mod;
fa[u] = v;
} /*输出答案 更新强制在线值*/
void print(int t)
{
Ans = (int)a[t];
printf("%d\n", Ans);
} /*快速读入*/
inline int read()
{
int f = 1, w = 0; char ch = getchar();
for(; (ch < '0') || (ch > '9'); ch = getchar()) if(ch == '-') f = -1;
for(; (ch >= '0') && (ch <= '9'); ch = getchar()) w = (w << 3) + (w << 1) + (int)(ch ^ '0');
return f * w;
} int main()
{
n = read(); q = read();
Setup();
while(q--)
{
opt = read();
if(opt == 1)
{
x = (read() + Ans) % n;
y = (read() + Ans) % n;
x = find(x); y = find(y);
line(x, y);
}
else
{
x = (read() + Ans) % n;
x = find(x);
print(x);
}
}
return 0;
}

P5689 多叉堆的更多相关文章

  1. AC日记——二叉堆练习3 codevs 3110

    3110 二叉堆练习3  时间限制: 3 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description 给定N(N≤500,000)和N个整 ...

  2. codevs 3110 二叉堆练习3

    3110 二叉堆练习3 http://codevs.cn/problem/3110/ 题目描述 Description 给定N(N≤500,000)和N个整数(较有序),将其排序后输出. 输入描述 I ...

  3. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  4. POJ 2010 - Moo University - Financial Aid 初探数据结构 二叉堆

    考虑到数据结构短板严重,从计算几何换换口味= = 二叉堆 简介 堆总保持每个节点小于(大于)父亲节点.这样的堆被称作大根堆(小根堆). 顾名思义,大根堆的数根是堆内的最大元素. 堆的意义在于能快速O( ...

  5. 二叉堆(一)之 图文解析 和 C语言的实现

    概要 本章介绍二叉堆,二叉堆就是通常我们所说的数据结构中"堆"中的一种.和以往一样,本文会先对二叉堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本 ...

  6. 二叉堆(二)之 C++的实现

    概要 上一章介绍了堆和二叉堆的基本概念,并通过C语言实现了二叉堆.本章是二叉堆的C++实现. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的C++实现(完整源码)4. 二叉堆的C++测试程 ...

  7. 二叉堆(三)之 Java的实现

    概要 前面分别通过C和C++实现了二叉堆,本章给出二叉堆的Java版本.还是那句话,它们的原理一样,择其一了解即可. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的Java实现(完整源码) ...

  8. 二叉堆(binary heap)

    堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因 ...

  9. 在A*寻路中使用二叉堆

    接上篇:A*寻路初探 GameDev.net 在A*寻路中使用二叉堆 作者:Patrick Lester(2003年4月11日更新) 译者:Panic 2005年3月28日 译者序 这一篇文章,是&q ...

随机推荐

  1. 使用 dotnet-monitor 分析.NET 应用程序

    dotnet-monitor 是 .NET Core 命令行接口 (CLI) 工具, 可以很方便的在dotnet环境中分析我们的应用程序,需要注意的是,目前它还只是一个实验性的工具 在这之前,我们使用 ...

  2. kill的使用

    Linux中Kill进程的N种方法 (2011-12-23 17:27:59) 转载▼ 标签: 杂谈 分类: ubuntu系统操作 常规篇: 首先,用ps查看进程,方法如下: $ ps -ef -- ...

  3. J.U.C关于Execute实现

    JAVASE5的Execute将为你管理Thread对象,是启动任务的优选方案 /***newCachedThreadPool在程序的执行过程中通常会创建于所需任务相同数量的线程即可以达到Intege ...

  4. JDK动态代理案例与原理分析

    一.JDK动态代理实现案例 Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlo ...

  5. 多年经验总结,写出最惊艳的 Markdown 高级用法

    点赞再看,养成习惯,微信搜索[高级前端进阶]关注我. 本文 GitHub https://github.com/yygmind 已收录,有一线大厂面试完整考点和系列文章,欢迎 Star. 最近在学习的 ...

  6. js 鼠标点击页面出现文字

    <script type="text/javascript"> var a_idx = 0; jQuery(document).ready(function($) { ...

  7. 【十天自制软渲染器】DAY 01:图形学学习建议与环境搭建

    推荐直接阅读博客原文,更新更及时,阅读体验更佳 「十天自制软渲染器」这个标题我承认标题党了.在对图形学一无所知的情况下想十天自制一个软渲染器,就好似一节课没上过却试图一个晚上看完<30 天精通 ...

  8. 剑指offer 面试题10:斐波那契数列

    题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0).n<=39 编程思想 知道斐波拉契数列的规律即可. 编程实现 class Solu ...

  9. 面试官:Netty的线程模型可不只是主从多Reactor这么简单

    笔者看来Netty的内核主要包括如下图三个部分: 其各个核心模块主要的职责如下: 内存管理 主要提高高效的内存管理,包含内存分配,内存回收. 网通通道 复制网络通信,例如实现对NIO.OIO等底层JA ...

  10. Linux 使用命令行上传下载文件

    基本语法: 服务器: 用户名@ip:/路径 scp 要拷贝的文件 要存放的文件 上传文件到服务器 # 把本地 source.md 文件上传到 152.116.113.13 服务器的/home目录 # ...