Solution -「牛客 31454H」Permutation on Tree
\(\mathscr{Description}\)
Link.
给定一棵含有 \(n\) 个点的有根外向树, 对于所有满足树形拓扑关系的结点遍历顺序 \(p_{1..n}\) 求出 \(\sum_{i=2}^n|p_{i-1}-p_i|\) 之和. 答案对 \((10^9+7)\) 取模.
\(\require{cancel} \cancel{n\le200}\) \(n\le5\times10^3\).
\(\mathscr{Solution}\)
膜拜理论信息学家 \(\textbf{OneInDark}\). 不仅踩爆标算, 还极大加速了 BI (Bunny Intelligence) 代码生成工具的开发进程.
由于是被讲明白的, 所以可能会比较 unmotivated 呜.
考虑枚举序列上相邻的编号 \(x,y\), 计数它们在多少个序列中出现.
直接表示出想要求的东西? 设 \(w=\operatorname{lca}(u,v)\), 令 \(f(x,y)\) 表示仅考虑 \(w\) 子树内的序列, \(x,y\) 贴贴且认为 \(x,y\) 子树内结点间无序的方案数. 注意这里为了方便转移, 我们钦定了一些无序关系.
为方便推导, 先预处理出 \(g(u)\) 表示 \(u\) 子树内的合法序列数量. 设 \(q_u\) 表示 \(u\) 的父亲, \(s_u\) 表示 \(u\) 的子树大小, 对于边界情况, \(q_x=q_y=w\) 时, 有
\]
即, 其他兄弟该怎么选怎么选; 对于 \(x,y\) 子树, 把 \(x,y\) 贴在一个位置之后只剩 \(s_w-2\) 个空位和 \(s_x+s_y-1\) 个结点需要放进去.
对于其余情况, \(f(x,y)\) 自然是从 \(f(q_x,y)\) 和 \(f(x,q_y)\) 转移. 考虑从 \(q_x\) 下降到 \(x\) 的过程, \(x\) 的兄弟们需要被确定顺序, 贴贴的位置继续下放没有影响, 因而
\]
即, 选出 \(x\) 的兄弟们的位置, 选出它们之间的顺序, 各自内部再定序, 乘上转移到的状态. \(q_y\) 的转移完全对称. 不过仅当 \(q_x\neq w\) 或者 \(q_y\neq w\) 时才会发生上述转移.
问题是, 用这玩意儿其实并不方便表示答案. 先假设 \(w\) 就是全局根, 令 \(r_w\) 表示 \(\operatorname{lca}\) 为 \(w\) 的"答案", 那么
\]
其中中间三项系数就是补上 \(x,y\) 的定序. 注意 \(x,y\) 一定是子树内最先选出来的, 而且拿去贴贴了, 所有带了一些 \(-1\) 的常数.
最后, 自然是把 \(r_w\) 的信息拿到真正的树根上去:
\]
组合意义比较简单, 注意 \(u\) 子树内有一对点在贴贴, 所以大小会减小 \(1\).
至于那些丑陋的 \setminus, 都可以在 \(g(u)\) 的基础上修修补补 \(\mathcal O(1)\) 个因子算出来. 精细处理一些需要使用的逆元, 可以做到严格的 \(\mathcal O(n^2)\).
所以最奇妙的地方还是这个 \(f\) 的状态设计. 无序有序的钦定方便了后续转移.
\(\mathscr{Code}\)
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
const int MAXN = 200, MOD = 1e9 + 7;
int n, rt, ecnt, head[MAXN + 5], fac[MAXN + 5], ifac[MAXN + 5];
int bino[MAXN + 5][MAXN + 5], ibino[MAXN + 5][MAXN + 5];
int fa[MAXN + 5], f[MAXN + 5][MAXN + 5];
int g[MAXN + 5], ig[MAXN + 5], siz[MAXN + 5], ans[MAXN + 5];
struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
inline int iabs(const int u) { return u < 0 ? -u : u; }
inline int mul(const int u, const int v) { return 1ll * u * v % MOD; }
inline void subeq(int& u, const int v) { (u -= v) < 0 && (u += MOD); }
inline int sub(int u, const int v) { return (u -= v) < 0 ? u + MOD : u; }
inline void addeq(int& u, const int v) { (u += v) >= MOD && (u -= MOD); }
inline int add(int u, const int v) { return (u += v) < MOD ? u : u - MOD; }
inline int mpow(int u, int v) {
int ret = 1;
for (; v; u = mul(u, u), v >>= 1) ret = mul(ret, v & 1 ? u : 1);
return ret;
}
inline void link(const int u, const int v) {
graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
}
inline void init() {
bino[0][0] = 1;
rep (i, 1, n) {
bino[i][0] = 1;
rep (j, 1, i) bino[i][j] = add(bino[i - 1][j - 1], bino[i - 1][j]);
}
rep (i, 0, n) {
ibino[i][0] = 1;
rep (j, 1, i) ibino[i][j] = mul(ibino[i][j - 1], bino[i][j]);
int s = mpow(ibino[i][i], MOD - 2);
per (j, i, 1) {
ibino[i][j] = mul(s, ibino[i][j - 1]);
s = mul(s, bino[i][j]);
}
}
}
inline void getG(const int u) {
g[u] = siz[u] = 1;
for (int i = head[u], v; i; i = graph[i].nxt) {
if ((v = graph[i].to) != fa[u]) {
fa[v] = u, getG(v), siz[u] += siz[v];
g[u] = mul(bino[siz[u] - 1][siz[v]], mul(g[u], g[v]));
}
}
ig[u] = mpow(g[u], MOD - 2);
}
inline int calc(const int x, const int y, const int u) {
int& ret = f[x][y];
if (ret) return ret;
if (fa[x] == u && fa[y] == u) {
ret = mul(g[u], mul(mul(ibino[siz[u] - 1][siz[x]],
ibino[siz[u] - siz[x] - 1][siz[y]]), mul(ig[x], ig[y])));
ret = mul(ret, bino[siz[u] - 2][siz[x] + siz[y] - 1]);
return ret;
}
if (fa[x] != u) {
addeq(ret, mul(mul(bino[siz[fa[x]] + siz[y] - 2]
[siz[fa[x]] - siz[x] - 1], mul(g[fa[x]], mul(ig[x],
ibino[siz[fa[x]] - 1][siz[x]]))), calc(fa[x], y, u)));
}
if (fa[y] != u) {
addeq(ret, mul(mul(bino[siz[fa[y]] + siz[x] - 2]
[siz[fa[y]] - siz[y] - 1], mul(g[fa[y]], mul(ig[y],
ibino[siz[fa[y]] - 1][siz[y]]))), calc(x, fa[y], u)));
}
return ret;
}
inline std::vector<int> solve(const int u) {
std::vector<int> sub;
int is = mpow(siz[u] - 1, MOD - 2);
for (int i = head[u], v; i; i = graph[i].nxt) {
if ((v = graph[i].to) != fa[u]) {
fa[v] = u;
addeq(ans[u], mul(iabs(u - v), mul(mul(siz[v], is), g[u])));
auto&& tmp(solve(v));
for (int x: sub) for (int y: tmp) {
int coe = mul(iabs(x - y) << 1, mul(g[x], mul(g[y],
bino[siz[x] + siz[y] - 2][siz[x] - 1])));
addeq(ans[u], mul(coe, calc(x, y, u)));
}
sub.reserve(sub.size() + tmp.size());
for (int y: tmp) sub.push_back(y);
}
}
return sub.push_back(u), sub;
}
inline void summary(const int u) {
int is = mpow(siz[u] - 1, MOD - 2);
for (int i = head[u], v; i; i = graph[i].nxt) {
if ((v = graph[i].to) != fa[u]) {
summary(v);
int coe = mul(g[u], mul(bino[siz[u] - 2][siz[v] - 1],
mul(ig[v], ibino[siz[u] - 1][siz[v]])));
addeq(ans[u], mul(ans[v], coe));
}
}
}
int main() {
// freopen("tree.in", "r", stdin);
// freopen("tree.out", "w", stdout);
scanf("%d %d", &n, &rt), init();
rep (i, 2, n) { int u, v; scanf("%d %d", &u, &v), link(u, v); }
getG(rt), solve(rt), summary(rt);
printf("%d\n", ans[rt]);
return 0;
}
Solution -「牛客 31454H」Permutation on Tree的更多相关文章
- Solution -「牛客 NOIP 模拟赛」打拳
\(\mathcal{Description}\) 现 \(2^n\) 个人进行淘汰赛,他们的战力为 \(1\sim 2^n\),战力强者能战胜战力弱者,但是战力在集合 \(\{a_m\}\) 里 ...
- 「牛客练习赛53A」超越学姐爱字符串
更好的阅读体验 Portal Portal1: Nowcoder Description 超越学姐非常喜欢自己的名字,以至于英文字母她只喜欢\(\textrm{"c"}\)和\(\ ...
- 「牛客CSP-S2019赛前集训营2」服务器需求
传送门 NowCoder 解题思路 考虑一种贪心选择方法:每次选出最大的 \(m\) 个 \(a_i\) 进行覆盖. 那么就会出现一种特殊情况,最高的那个 \(a_i\) 需要多次选择,而且不得不每次 ...
- 「牛客CSP-S2019赛前集训营1」仓鼠的石子游戏
传送门 NowCoder 解题思路 考虑这样一件事:在任何的同一个石圈,后手肯定会输. 证明很简单,手玩一下就可以大致意会. 但是有一种特殊情况,就是大小为1的圈,这种圈就是起到一次交换先后手的作用, ...
- Solution -「多校联训」假人
\(\mathcal{Description}\) Link. 一种物品有 长度 和 权值 两种属性,现给定 \(n\) 组物品,第 \(i\) 组有 \(k_i\) 个,分别为 \((1,a ...
- Solution -「多校联训」古老的序列问题
\(\mathcal{Description}\) Link. 给定序列 \(\{a_n\}\),和 \(q\) 次形如 \([L,R]\) 的询问,每次回答 \[\sum_{[l,r]\su ...
- Solution -「洛谷 P4389」付公主的背包
\(\mathcal{Description}\) Link. 容量为 \(n\),\(m\) 种物品的无限背包,求凑出每种容量的方案数,对 \(998244353\) 取模. \(n,m ...
- Solution -「2020.12.26」 模拟赛
0x00 前言 一些吐槽. 考得很变态诶,看每道题平均两秒的时限就知道了... T1 降智了想到后缀懒得打. T2 口胡了假优化,结果和暴力分一样?? T3 黑题还绑点?? \(50 + 80 + 0 ...
- 「博客美化」I 页面的CSS
要有自己的CSS十分重要 可以改别人写的CSS代码 也可以改博客园模板 我这里改的是SympleMomery 别忘了禁用模板 /*......去除广告..........*/ div[id^=&quo ...
- Note/Solution -「洛谷 P5158」「模板」多项式快速插值
\(\mathcal{Description}\) Link. 给定 \(n\) 个点 \((x_i,y_i)\),求一个不超过 \(n-1\) 次的多项式 \(f(x)\),使得 \(f(x ...
随机推荐
- 在使用asm包进行动态类加载的时候的打包问题
如图所示,开发时使用的jdk包下面的asm包,在进行打包时提示asm包不存在,打包方式使用如下: 目前提供两种解决方案: 1:修改打包方式,将jdk的包也打进去: <plugin> < ...
- C# 单例模式的多种实现
单例模式介绍 单例模式是一种创建型设计模式,它主要确保在一个类只有一个实例,并提供一个全局访问点来获取该实例.在C#中,有多种方式实现单例模式,每种方式都有其特定的使用场景和注意事项. 设计模式的作用 ...
- git clone失败,超时,速度慢
最近使用git这个工具,发现git clone指令经常由于网络问题导致失败.查找相关资料之后,找到办法为修改网址,具体为: 将 git clone https://github.com/alibaba ...
- HAL+CubeIDE,STM32F407ZGT6正点原子探索者,舵机驱动,从零开始
CubeIDE_HAL库_从零开始玩舵机 1.材料准备 开发板:正点原子STM32F407ZGT6探索者 舵机:SG90 舵机线材分辨:褐色 / 红色 / 橘黄色 -- GND / VCC / PWM ...
- GIt分布式管理工具
Git(分布式版本控制工具) Git的学习是不依赖我们前面学习的知识,就算没有学习java也可以学习 Git就是一个类似于百度云盘的仓库 重点是要掌握使用idea操作Git,企业用的最多,一般不会去使 ...
- toFullScreen:全屏------exitFullscreen:退出全屏
toFullScreen:全屏 function toFullScreen(){ let elem = document.body; elem.webkitRequestFullScreen ? el ...
- AI 实战篇:Spring-AI再更新!细细讲下Advisors
在2024年10月8日,Spring AI再次进行了更新,尽管当前版本仍为非稳定版本(1.0.0-M3),但博主将持续关注这些动态,并从流行的智能体视角深入解析其技术底层.目前,Spring AI仍处 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 14 期(2024年11.18-11.24)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- SpringMVC-Mybatis-Maven项目整合
上次不知道为什么,把写好的系列文章都搞成一样了.结果,五篇文章,都变成了最后一篇文章. 悲剧,好吧,只好重新写了. 这系列文章写的是SpringMVC-Mybatis-Maven项目整合.说白了,其实 ...
- Java 并发编程实战学习笔记——CountDownLatch的使用
public class CountDownLatch extends Object 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 Co ...