【数学】prufer 序列
题目描述
请实现 Prüfer 序列和无根树的相互转化。
为方便你实现代码,尽管是无根树,我们在读入时仍将
对于一棵无根树,设
另外,对于一个长度为
算法描述
prufer 序列,用于将一个
无根树转化为 prufer 序列
考虑每次在叶节点当中选择编号最小的那一个,删除它和它连的边,在 prufer 序列中加入它指向的那个点,直到只有两个点为止。
我们发现,对于任意一棵无根树,一定可以转化为一个序列(树总有叶节点),而且过程当中每一步都是唯一的,所以树可以映射到序列上。
这样做用堆维护是
事实上可以用指针简单的代替这一过程,只需要每次右移,如果不是叶节点就不管,如果是就删掉,观察删掉过后有没有可能产生新的叶节点,并且如果产生了,这个点编号是否小于当前的指针,如果是的话不移动指针,继续删这个点就好了。
这样就解决了 “编号最小” 的问题,由于每个点只会被删一次,所以是
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 序列一个显然的性质就是无根树上
所以我们可以通过序列得到点的度数,就可以知道第一步时,谁是最小的叶节点,这个点也是唯一的。
所以我们直接维护,连一条
考虑结束以后,我们才连了
我们发现 “编号最小的叶子” 这个事情怎么样也轮不到
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 公式:
个点的有标号无根树的数量是 。
考虑 prufer 序列的值域是 手模可以发现,任意一个长为
相关题目:
P6086 【模板】Prufer 序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P4430 小猴打架 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
上面两道是板题,下面用另一道板题作为例题:
Clues - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给定一个
设
将按照边计数转化为按照点计数:
容易发现,一棵树的 prufer 序列每一项的
所以答案就是:
所以我们要求所有的
所以最终答案就是:
直接跑连通块
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!