首页 > 其他分享 >【数学】prufer 序列

【数学】prufer 序列

时间:2023-11-20 20:24:18浏览次数:31  
标签:int fa 数学 序列 now prufer deg

题目描述

请实现 Prüfer 序列和无根树的相互转化。

为方便你实现代码,尽管是无根树,我们在读入时仍将 \(n\) 设为其根。

对于一棵无根树,设 \(f_{1\dots n-1}\) 为其父亲序列(\(f_i\) 表示 \(i\) 在 \(n\) 为根时的父亲),设 \(p_{1 \dots n-2}\) 为其 Prüfer 序列

另外,对于一个长度为 \(m\) 的序列 \(a_{1 \dots m}\),我们设其权值为 \(\operatorname{xor}_{i = 1}^m i \times a_i\)。

\(1 \leq n \leq 5 \times 10^6\) 。

算法描述

prufer 序列,用于将一个 \(n\) 个点的有标号无根树映射到一个长为 \(n - 2\) 的序列上。下面讲解实现过程。

无根树转化为 prufer 序列

考虑每次在叶节点当中选择编号最小的那一个,删除它和它连的边,在 prufer 序列中加入它指向的那个点,直到只有两个点为止。

我们发现,对于任意一棵无根树,一定可以转化为一个序列(树总有叶节点),而且过程当中每一步都是唯一的,所以树可以映射到序列上。

这样做用堆维护是 \(\Theta(n \log n)\) 的。

事实上可以用指针简单的代替这一过程,只需要每次右移,如果不是叶节点就不管,如果是就删掉,观察删掉过后有没有可能产生新的叶节点,并且如果产生了,这个点编号是否小于当前的指针,如果是的话不移动指针,继续删这个点就好了。

这样就解决了 “编号最小” 的问题,由于每个点只会被删一次,所以是 \(\Theta(n)\) 的。

inline void Tree_to_prufer()
{
	for(int i = 1;i <= n - 1;i++) deg[fa[i]]++,deg[i]++;
	int cnt = 0;
	for(int tp = 1;tp <= n;tp++)
	{
		if(deg[tp] > 1) continue;
		int now = tp;
		do{
			p[++cnt] = fa[now];
			deg[fa[now]]--; deg[now]--;
			if(deg[fa[now]] == 1) now = fa[now];
			else now = n + 2; 
		}while(now < tp);
	}
}
prufer 序列转化为无根树

我们研究上面的过程,第一步一定是选了 prufer 序列的第一个点和最小的叶节点连边。

prufer 序列一个显然的性质就是无根树上 \(x\) 的度数等于它在 prufer 序列中出现的次数 \(+1\) 。

所以我们可以通过序列得到点的度数,就可以知道第一步时,谁是最小的叶节点,这个点也是唯一的。

所以我们直接维护,连一条 \(p_1 \to leaf\) 的边,然后将两个度数都减 \(1\),类似上面的过程,每次找到编号最小的叶节点即可,优化方法和上面一样。

考虑结束以后,我们才连了 \(n - 2\) 条边,但是总共有 \(n - 1\) 条边。

我们发现 “编号最小的叶子” 这个事情怎么样也轮不到 \(n\) 。所以剩下的一定是 \(n\) 和另外一个点,连起来即可,也是 \(\Theta(n)\) 的。

inline void Prufer_to_tree()
{
	for(int i = 1;i <= n - 2;i++) deg[p[i]]++;
	for(int i = 1;i <= n;i++) deg[i]++;
	for(int i = 1,tp = 1;i <= n - 2;tp++) 
	{
		if(deg[tp] > 1) continue;
		int now = tp;
		do{
			fa[now] = p[i];
			deg[now]--; deg[p[i]]--;
			if(deg[p[i]] == 1) now = p[i];
			else now = n + 2;
			i++;
		}while(now < tp && i <= n - 2);
	}
	for(int i = 1;i < n;i++) if(deg[i] > 0) {fa[i] = n; break;}
}

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 5e6 + 5;
int deg[N],fa[N],p[N],n;
inline void Tree_to_prufer()
{
	for(int i = 1;i <= n - 1;i++) deg[fa[i]]++,deg[i]++;
	int cnt = 0;
	for(int tp = 1;tp <= n;tp++)
	{
		if(deg[tp] > 1) continue;
		int now = tp;
		do{
			p[++cnt] = fa[now];
			deg[fa[now]]--; deg[now]--;
			if(deg[fa[now]] == 1) now = fa[now];
			else now = n + 2; 
		}while(now < tp);
	}
}
inline void Prufer_to_tree()
{
	for(int i = 1;i <= n - 2;i++) deg[p[i]]++;
	for(int i = 1;i <= n;i++) deg[i]++;
	for(int i = 1,tp = 1;i <= n - 2;tp++) 
	{
		if(deg[tp] > 1) continue;
		int now = tp;
		do{
			fa[now] = p[i];
			deg[now]--; deg[p[i]]--;
			if(deg[p[i]] == 1) now = p[i];
			else now = n + 2;
			i++;
		}while(now < tp && i <= n - 2);
	}
	for(int i = 1;i < n;i++) if(deg[i] > 0) {fa[i] = n; break;}
}
int main()
{
	int op;
	scanf("%d%d",&n,&op);	
	if(op == 1) 
	{
		for(int i = 1;i <= n - 1;i++) scanf("%d",&fa[i]);
		Tree_to_prufer();
		long long ans = 0;
		for(int i = 1;i <= n - 2;i++) ans ^= 1ll * i * p[i];
		printf("%lld",ans);
	}
	else
	{
		for(int i = 1;i <= n - 2;i++) scanf("%d",&p[i]);
		Prufer_to_tree();
		long long ans = 0;
		for(int i = 1;i <= n - 1;i++) ans ^= 1ll * i * fa[i];
		printf("%lld",ans);
	}
	return 0;
 } 

prufer 序列的用法

著名的 Cayley 公式:

\(n\) 个点的有标号无根树的数量是 \(n^{n - 2}\)。

考虑 prufer 序列的值域是 \([1,n]\) ,而且手模可以发现,任意一个长为 \(n - 2\) 的,值域 \([1,n]\) 的数列都可以转化成一个无根树,而且可以用上述方式映射回来,所以这是一个一一对应关系,prufer 序列的数量就是无根树的数量。

相关题目:

P6086 【模板】Prufer 序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P4430 小猴打架 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

上面两道是板题,下面用另一道板题作为例题:

Clues - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

给定一个 \(n\) 个点 \(m\) 条边的带标号无向图,它有 \(k\) 个连通块,求添加 \(k-1\) 条边使得整个图连通的方案数,答案对 \(p\) 取模。

\(1 \leq n \leq 10^5,0 \leq m \leq 10^5,1 \leq p \leq 10^9\)。

设 \(a_i\) 是第 \(i\) 个连通块的大小,考虑枚举每一棵树 \(T\) ,答案就是:

\[\sum_T \prod_{(u,v) \in T} a_ua_v \]

将按照边计数转化为按照点计数:

\[\sum_T \prod_{i = 1}^k a_i^{deg_i} \]

容易发现,一棵树的 prufer 序列每一项的 \(a\) 值相乘的结果是:

\[\prod_{i = 1}^k a_i^{deg_i - 1} \]

所以答案就是:

\[\sum_{s \in prufer} \prod_{i = 1}^{k - 2}a_{s_i} · \prod_{i = 1}^k a_i \]

所以我们要求所有的 \(k\) 项 \(prufer\) 序列的 \(a\) 值乘积的和,假设 \(f_i\) 为前 \(i\) 项所有序列的和,不难发现最后一项填什么都可以,所以:

\[f_i = f_{i - 1} \times \sum_{i = 1}^ka_i \]

\[f_i = f_{i - 1} \times n \]

\[f_{k - 2} = n^{k - 2} \]

所以最终答案就是:

\[n^{k - 2} \times \prod_{i = 1}^k a_i \]

直接跑连通块 \(\Theta(n)\) 计算即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n,m,MOD;
struct Edge{
	int v,next;
}e[N * 2];
int vis[N],head[N],tot = 0,a[N],cnt = 0;
typedef long long ll;
ll f[N];
inline void add(int x,int y)
{
	++tot;
	e[tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
}
inline void dfs(int x,int num)
{
	++a[num];
	vis[x] = 1;
	for(int i = head[x];i;i = e[i].next)
	{
		int to = e[i].v;
		if(vis[to]) continue;
		dfs(to,num);
	}
}
inline ll ksm(ll base,int pts)
{
	ll ret = 1;
	for(;pts > 0;pts >>= 1,base = base * base % MOD)
		if(pts & 1)
			ret = ret * base % MOD;
	return ret;
 } 
int main()
{
	cin>>n>>m>>MOD;
	for(int i = 1,x,y;i <= m;i++)
	{
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	if(MOD == 1) {puts("0"); return 0;}
	memset(vis,0,sizeof(vis));
	for(int i = 1;i <= n;i++) if(!vis[i]) {++cnt; dfs(i,cnt);}
	ll prod = 1;
	for(int i = 1;i <= cnt;i++) prod = prod * a[i] % MOD;
	if(cnt == 1) {puts("1"); return 0;}
	cout<<prod * ksm(n,cnt - 2) % MOD;
	return 0;
}

标签:int,fa,数学,序列,now,prufer,deg
From: https://www.cnblogs.com/TheLastCandy/p/17844755.html

相关文章

  • 数学分析(I)
    1求极限:\[\lim_{x\to0}\frac{\sin(x^2\sin\frac1x)}x\]如果直接把\(\sin(x^2\sin\frac1x)\)用等价无穷小变成\(x^2\sin\frac1x\)是有问题的。因为\(\lim_{x\to0}\frac{x^2\sin\frac1x}{\sin(x^2\sin\frac1x)}\)不存在,原因是任意邻域都有分母为\(0\)的点。......
  • playwright无序列表
    listitem是无序列表,ul和li标签组合•1.水平显示的列表•2.dropdown方式,一般需要鼠标悬停,出现对应的列表 1.#listitem定位,role角色定位到listitem上面在通过filter定位某一个文本page.get_by_role('listitem').filter(has_text='内容').click()2.#先定位点......
  • springboot 控制序列化反序列化示例(接口返回数据处理/接口接收数据处理)
    1.返回Long转JSONpackagecom.mingx.drone.config;importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.JsonSerializer;importcom.fasterxml.jackson.databind.SerializerProvider;importjava.io.IOException;/***@Descript......
  • 2023 互测 R2T1 序列的线性做法
    把原题做法GF的系数进行OEIS,发现那个三角形就是Catalan数的GF复合上一个\(xy(1-x)\)的形式。更为奇妙的是,OEIS下面竟然给出了一个通项公式,\(T(n,k)=(-1)^{n-k}{k\choosen-k}C_k\),其中\(C\)是Catalan数列。代入原题的式子,发现答案竟然就是:\[\sum_{i=0}^n(-1)^{n......
  • Java基础知识回顾5-序列化和反序列化
    一、概念Java序列化是指把Java对象转换为字节序列的过程。Java反序列化是指把字节序列恢复为Java对象的过程。序列化作用:在传递和保存对象时,保存对象的完整性和可传递性,对象转换为字节流,可以站网络上传输或者保存在本地文件中。反序列化作用:根据字节流中保存的对象状态及描述信息......
  • java反序列化----CC7利用链学习笔记(Hashtable)
    目录java反序列化----CC7利用链学习笔记(Hashtable)环境搭建利用链java反序列化----CC7利用链学习笔记(Hashtable)环境搭建jdk8u71<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId>......
  • 【Django-DRF用法】多年积累md笔记,第(4)篇:Django-DRF反序列化详解
    本文从分析现在流行的前后端分离Web应用模式说起,然后介绍如何设计RESTAPI,通过使用Django来实现一个RESTAPI为例,明确后端开发RESTAPI要做的最核心工作,然后介绍DjangoRESTframework能帮助我们简化开发RESTAPI的工作。全套DRF笔记直接地址:请移步这里共5章,24子模块,总计1......
  • java反序列化----CC6利用链学习笔记(HashMap和HashSet)
    目录java反序列化----CC6利用链学习笔记环境配置利用链java反序列化----CC6利用链学习笔记环境配置jdk8(无版本要求)pom.xml中写入<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId>......
  • [CTF/Web] PHP 反序列化学习笔记
    Serialize&unserialize这两个方法为PHP中的方法,参见serialize和unserialize的官方文档.以下内容中可能存在字段,属性,成员三个名词误用/混用,但基本都表示属性文章仍在完善之中,SESSION反序列化漏洞要学废了入门我们先看看方法的序列化之后的字符串的......
  • java反序列化----CC5利用链学习笔记
    java反序列化----CC5利用链学习笔记目录java反序列化----CC5利用链学习笔记环境配置利用链TiedMapEntryBadAttributeValueExpException参考文章环境配置jdk8u(无java版本要求)pom.xml中写入<dependency><groupId>commons-collections</groupId>......