首页 > 其他分享 >Prufer 序列

Prufer 序列

时间:2025-01-20 10:09:51浏览次数:1  
标签:int ll 序列 rm Prufer mod

可以用来做一些树上计数问题,相当于一个转化工具。主要用在生成树的问题上。

定义

考虑一棵无根树 \(\mathrm T\),定义度数为 \(1\) 的节点为叶子节点,令叶子节点集合为 \(S\)。每次找到 \(S\) 中编号最小的元素,并把 它连接的那个节点 加入到序列末端,并把这个元素删去,直到只剩两个元素为止。

那么现在显然可以使用堆来维护 \(S\),可以做到 \(O(n \log n)\) 构造。

但事实上,有更优秀的 \(O(n)\) 做法。

考虑从 \(1\) 到 \(n\) 扫描,假设现在遍历到点 \(u\),且满足 \(1, 2, ...., u - 1\) 都不是叶子节点 ,有两种情况:

  • \(u\) 不是叶子节点:直接跳过即可。

  • \(u\) 是叶子节点:设它连接的那个节点为 \(fa_u\),把 \(fa_u\) 加入序列中,把 \(u\) 删去。然后考虑更新 \(S\),不难发现它只影响了 \(fa_u\)。假如 \(fa_u < u\) 且 \(fa_u \in S\),那么重复这个过程,并且再考虑 \(fa_{fa_u}\),直到不满足上面的条件。

不难发现,这个过程中,每个点至多被访问 \(2\) 次,且 \(u\) 单调递增,因此时间复杂度为 \(O(n)\)。

qwq
for(int i = 1; i <= n - 1; i++){
	if(tot == n - 2) continue;
	if(deg[i] == 1){
		ans[++tot] = fa[i]; deg[fa[i]]--; int p = fa[i];
		while(deg[p] == 1 && p < i && tot < n - 2) ans[++tot] = fa[p], deg[fa[p]]--, p = fa[p]; 
	}
}

性质

1. \(\mathrm {Prufer}\) 序列的长度为 \(n - 2\),点 \(i\) 在 \(\rm Prufer\) 序列中出现 \(deg_i - 1\) 次(\(deg_i\) 为点 \(i\) 的度数)。

证明非常显然。

应用: 当我们在用 \(\rm Prufer\) 序列还原树的时候,可以还原出每个节点的度数,从而知道叶子节点集合,于是就知道了每个点在 \(\rm Prufer\) 序列中出现时,另一个节点是哪一个叶子,实现与上面那个相似。

2. 每个 \(\rm Prufer\) 序列与一个有标号无根树唯一对应,即 \(\rm Prufer\) 序列与有标号无根树 构成双射

证明:从构造过程中可以发现,一个 \(\rm Prufer\) 序列唯一对应一棵树,而每棵树都有唯一的合法的 \(\rm Prufer\) 序列。

3. 由 \(n\) 个点组成的有标号无根树的个数一共有 \(n^{n - 2}\) 个。

证明: 由于有标号无根树与 \(\rm Prufer\) 序列构成双射,因此可以直接对 \(\rm Prufer\) 序列进行计数。由于每个 \(\rm Prufer\) 序列都是合法的,所以前面 \(n - 2\) 位置随便放都可以。

例题:

CF156D Clues

首先把每个联通块看成一个大点,这样由 \(\rm Prufer\) 序列的性质,树的形态为 \(n^{k - 2}\),然后不难发现,假如我们随便钦定一个点为根,可以把每条边都归到儿子处,这样每个属于每个联通块的边都只有一条了,方案树为 \(\prod s_i\),\(s_i\) 为联通块大小。

CF917D Stranger Trees

首先无脑二项式繁衍,转化为求钦定的数量。假设现在钦定了 \(k\) 条边,那么就会有 \(n - k\) 个联通块,这下就转化为了求 \(n - k\) 个联通块连成一颗树的方案树了。同上一道题,方案数为 \(n^{n - k - 2} \prod s_i\)。容易直接树形 \(\rm DP\) 做到 \(O(n^3)\)。但是不妨考虑 \(\prod s_i\) 原来的意义,其实就是在每个联通块中选一个点的方案数,直接 \(f_{u, j, 0/1}\) 设点 \(u\) 子树内,选了 \(j\) 个联通块,且 \(u\) 的联通块中是否已经选了点,树上背包合并即可。

qwq
#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
using namespace std;

const int N = 500 + 10;
const ll mod = 1e9 + 7;

void ADD(ll& x, ll y){x += y; (x >= mod) ? x -= mod : 0;}

int n, siz[N];
ll f[N][N][2], jc[N], jcinv[N], lstf[N][N][2]; 
vector<int> vec[N];

ll qpow(ll x, int y){
    ll ret = 1; if(y < 0) x = qpow(x, mod - 2), y = -y;
    for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod;
    return ret;
}
ll C(int x, int y){
    return jc[x] * jcinv[x - y] % mod * jcinv[y] % mod;
} 

void dfs(int u, int fa){
    f[u][1][0] = f[u][1][1] = 1; siz[u] = 1;
    for(auto v : vec[u]){
        if(v == fa) continue;
        dfs(v, u);
        for(int i = 1; i <= siz[u]; i++) lstf[u][i][0] = f[u][i][0], lstf[u][i][1] = f[u][i][1], f[u][i][1] = f[u][i][0] = 0;
        for(int i = 1; i <= siz[u]; i++){
            for(int j = 1; j <= siz[v]; j++){
                ADD(f[u][i + j][0], lstf[u][i][0] * f[v][j][1] % mod);
                ADD(f[u][i + j][1], lstf[u][i][1] * f[v][j][1] % mod);
                ADD(f[u][i + j - 1][0], lstf[u][i][0] * f[v][j][0] % mod);
                ADD(f[u][i + j - 1][1], (lstf[u][i][1] * f[v][j][0] % mod + lstf[u][i][0] * f[v][j][1] % mod) % mod);
            }
        } siz[u] += siz[v];
    }
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i < n; i++){
        int x, y; cin >> x >> y;
        vec[x].pb(y); vec[y].pb(x);
    }
    dfs(1, 0); jc[0] = jcinv[0] = 1;
    for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mod;
    jcinv[n] = qpow(jc[n], mod - 2);
    for(int i = n - 1; i > 0; i--) jcinv[i] = jcinv[i + 1] * (i + 1) % mod;
    for(int i = 0; i < n; i++){
        ll ans = 0;
        for(int j = i; j <= n; j++) ADD(ans, (((j - i) & 1) ? mod - 1 : 1ll) * C(j, i) % mod * f[1][n - j][1] % mod * qpow(n, n - j - 2) % mod);
        cout << ans << " ";
    }

    return 0;
}**

标签:int,ll,序列,rm,Prufer,mod
From: https://www.cnblogs.com/little-corn/p/18680813

相关文章

  • 找出文本中含有特定拼音的汉字序列
    文本预处理withopen('C:/Users/tellw/Desktop/假面山庄杀人事件.txt',encoding='utf8')asf: contents=f.read()contents=''.join(contents.split('\n'))importreimportsyscontents=re.sub(r'[0-9a-zA-Z]|\W','......
  • 【华为OD-E卷 - 最长连续子序列 100分(python、java、c++、js、c)】
    【华为OD-E卷-最长连续子序列100分(python、java、c++、js、c)】题目有N个正整数组成的一个序列。给定整数sum,求长度最长的连续子序列,使他们的和等于sum,返回此子序列的长度,如果没有满足要求的序列,返回-1输入描述第一行输入是:N个正整数组成的一个序列第二行输入是:给定......
  • 如何在仿真分析,预测中 应用风力发电、光伏发电数据、用电负荷数据集,构建一个全面的时
    风力发电、光伏发电数据、用电负荷数据集,用于仿真分析,预测等。[1]光伏数据,光伏出力年光伏数据集,包含光伏发电功率、多种类型光照辐射强度数据(DNI,DHI,GHI),附带多种数据,可用于光伏场景生成与缩减、光伏特性分析、光伏优化调度等。[2]光伏预测功率数据2007-2020光伏预测功......
  • _EMD-KPCA-LSTM 基于经验模态分解和核主成分分析的长短期记忆网络多维时间序列预测_ma
    EMD-KPCA-LSTM基于经验模态分解和核主成分分析的长短期记忆网络多维时间序列预测MATLAB代码(含LSTM、EMD-LSTM、EMD-KPCA-LSTM三个模型的对比)matlab参考文档:基于EMD-PCA-LSTM的光伏功率预测模型研究内容:本案例使用数据集是北半球光伏功率,共四个输入特征(太阳辐射度气温......
  • 循环神经网络(RNN)与序列数据处理:亦菲彦祖的时序分析
    循环神经网络(RNN)与序列数据处理:亦菲彦祖的时序分析亲爱的亦菲彦祖,欢迎来到“时间的国度”!在之前的文章中,我们主要聚焦于深度学习在视觉领域的应用(如CNN)。然而,现实世界中有许多数据都存在时间或顺序上的依赖关系,例如自然语言、时间序列、视频数据等。为了处理这些序列数据,深度......
  • LeetCode:491.递增子序列
    跟着carl学算法,本系列博客仅做个人记录,建议大家都去看carl本人的博客,写的真的很好的!代码随想录LeetCode:491.递增子序列给你一个整数数组nums,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素。你可以按任意顺序返回答案。数组中可能含有重复......
  • 从零开始的PHP原生反序列化漏洞
    1、写在前面OK兄弟们,这几天一直在面试,发现很多HR喜欢问反序列化相关的内容,今天咱们就从最简单的PHP原生反序列化入手,带大家入门反序列化2、PHP序列化在PHP中,有反序列化,就有序列化,我们先来解释一下序列化。所谓序列化,就是将PHP的一个对象,序列化为一串字符串的过程,其......
  • qt之读写二进制文件(序列化方式)
    除文本文件外,其他文件都可以看做是二进制文件,可以单独使用QFile读写二进制文件,但一般结合使用QFile和QDataStream读写二进制文件。头文件部分主要代码private:QStringm_filename;template<classT>voidwriteByStream(Tvalue);template<classT>voidread......
  • Prometheus中Sample(样本)与Series(序列)的区别详解
    Prometheus中Sample(样本)与Series(序列)的区别详解 在Prometheus这一强大的开源监控和警报系统中,Sample(样本)与Series(序列)是两个核心概念,它们在数据模型和数据处理流程中扮演着至关重要的角色。本文将详细探讨这两个概念的定义、组成、作用以及它们之间的区别。一、Sample(样本)1.1......
  • 【c++】【算法】【动态规划】最长公共子序列
    【c++】【算法】【动态规划】最长公共子序列//递归方式//最长公共子序//直接递归求最长公共子序长度intFindValue(conststring&X,conststring&Y,inti,intj){ if(i==0||j==0)return0; if(X[i]==Y[j])returnFindValue(X,Y,i-1,j-1)+1; ......