zoukankan      html  css  js  c++  java
  • 【学习笔记】prufer序列 洛谷P6086

    题目链接

    算法分析

    (我似乎之前学过两遍(prufer),但是我咕咕咕了,而且板子也不知道丢到哪里去了(难怪我这么菜

    树 -> prufer 序列

    算法流程:

    1. 找到树中编号最小的入度为(1)的结点(叶结点)
    2. 删去该结点,并将与该结点相邻结点的标号加入(prufer)序列
    3. 重复(1,2)操作(n-2)次,直到图中只剩下两个结点。

    实现方式:

    1. 每次暴力枚举编号最小的叶结点,进行操作。
    2. 堆优化。把度数变为(1)的点丢到堆里去,每次从堆里取结点,复杂度(O(nlogn))
    3. 用指针。指针指向编号最小的叶结点,每次删掉之后,如果产生了新的叶结点,并且比指针指向的下一个更小,就直接删掉新产生的那个,否则指针自增找到下一个编号最小的叶结点,(其实没必要存图了)复杂度(O (n))

    一些性质

    1. 叶结点在(prufer)序列中不会出现
    2. 剩下的两个结点中,有一个结点的编号是最大的编号(n)
    3. 一个结点在(prufer)序列出现的次数为它度数(-1)
    4. (n)个点的无向完全图的生成树计数,即(n)个点的有编号无根树计数:(n^{n-2})
    5. (n)个点的有编号有根树的计数:(n^{n-2} imes n=n^{n-1})(无根树选一个节点作为根,所以( imes n)
    void T_to_P()
    {
    	for(int i=1;i<=n-1;i++)
    		f[i]=rd(),d[f[i]]++;//儿子个数 
    	for(int i=1,j=1/*指针*/;i<=n-2;i++,j++)
    	{
    		while(d[j]) j++;//找到下一个叶结点 
    		p[i]=f[j];//将叶结点的爸爸记进prufer序列 
    		d[f[j]]--;
    		while(i<=n-2&&d[p[i]]==0&&p[i]<j) p[i+1]=f[p[i]],d[p[i+1]]--,i++;//如果爸爸变成了叶结点并且比j小 
    	}
    	LL ans=0;
    	for(int i=1;i<=n-2;i++)
    		ans=ans^(1ll*i*p[i]);
    	printf("%lld
    ",ans);
    }
    

    prufer 序列 -> 树

    算法流程:

    (其实就是上述算法反过来而已

    1. 根据(prufer)序列的性质,可以算出每个点的度数。
    2. 找到一个编号最小的度数为(1)的结点。
    3. 将该结点与当前的(prufer)序列的点相连,然后同时减去这两个点的度数。
    4. 重复(2,3)操作(n-2)次,只剩下两个结点。
    5. 将剩下的两个结点相连。

    实现方式:

    1. 暴力枚举最小的度数为(1)的点。
    2. 堆优化。一样的,就不说了。
    3. 用指针。指针刚开始指向编号最小的度数为(1)的结点,每次连接之后,如果度数减掉之后产生了新的度数为(1)的结点,并且比指针指向的更小,就继续把新产生的结点搞掉,否则还是指针继续往下枚举找到下一个编号最小的度数为(1)的结点。
    void P_to_T()
    {
    	for(int i=1;i<=n-2;i++)
    		p[i]=rd(),d[p[i]]++;//数在prufer序列出现的次数是度数-1 
    	p[n-1]=n;
    	//最后剩下的结点中一定有n 并且另一个点的编号小于n 可以把它放进来 就不用单独连最后两个点
    	for(int i=1,j=1;i<=n-1;i++,j++)
    	{
    		while(d[j]) j++;
    		f[j]=p[i];
    		d[f[j]]--;
    		while(i<=n-1&&d[p[i]]==0&&p[i]<j) f[p[i]]=p[i+1],d[p[i+1]]--,i++;
    		//如果爸爸(j的爸爸 是p[i])变成了度为1并且比j小 那么爸爸和下一个prufer序列里的数相连
    	}
    	LL ans=0;
    	for(int i=1;i<=n-1;i++)
    		ans=ans^(1ll*i*f[i]);
    	printf("%lld
    ",ans); 
    }
    

    ►Code View

    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    #define N 5000005
    #define INF 0x3f3f3f3f
    #define LL long long
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	return f*x;
    }
    int n,opt;
    int f[N],p[N],d[N];
    void T_to_P()
    {
    	for(int i=1;i<=n-1;i++)
    		f[i]=rd(),d[f[i]]++;//儿子个数 
    	for(int i=1,j=1/*指针*/;i<=n-2;i++,j++)
    	{
    		while(d[j]) j++;//找到下一个叶结点 
    		p[i]=f[j];//将叶结点的爸爸记进prufer序列 
    		d[f[j]]--;
    		while(i<=n-2&&d[p[i]]==0&&p[i]<j) p[i+1]=f[p[i]],d[p[i+1]]--,i++;//如果爸爸变成了叶结点并且比j小 
    	}
    	LL ans=0;
    	for(int i=1;i<=n-2;i++)
    		ans=ans^(1ll*i*p[i]);
    	printf("%lld
    ",ans);
    }
    void P_to_T()
    {
    	for(int i=1;i<=n-2;i++)
    		p[i]=rd(),d[p[i]]++;//数在prufer序列出现的次数是度数-1 
    	p[n-1]=n;
    	//最后剩下的结点中一定有n 并且另一个点的编号小于n 可以把它放进来 就不用单独连最后两个点
    	for(int i=1,j=1;i<=n-1;i++,j++)
    	{
    		while(d[j]) j++;
    		f[j]=p[i];
    		d[f[j]]--;
    		while(i<=n-1&&d[p[i]]==0&&p[i]<j) f[p[i]]=p[i+1],d[p[i+1]]--,i++;
    		//如果爸爸(j的爸爸 是p[i])变成了度为1并且比j小 那么爸爸和下一个prufer序列里的数相连
    	}
    	LL ans=0;
    	for(int i=1;i<=n-1;i++)
    		ans=ans^(1ll*i*f[i]);
    	printf("%lld
    ",ans); 
    }
    int main()
    {
    	n=rd(),opt=rd();
    	if(opt==1) T_to_P();
    	else P_to_T();
    	return 0;
    }
    
    
    
  • 相关阅读:
    剖析VC++函数调用约定转
    C++的坑真的多吗?转
    An Introduction to LockFree Programming转
    __cdecl __stdcall区别转
    学习PHP感谢帅哥分享O(∩_∩)O~
    28个Unix/Linux的命令行神器转
    C++ 对象的内存布局(上)转
    一个fork的面试题转
    20本最好的Linux免费书籍转
    谁说外国人都很文明
  • 原文地址:https://www.cnblogs.com/lyttt/p/14032700.html
Copyright © 2011-2022 走看看