把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ3899】仙人掌树的同构(圆方树+树形DP)

点此看题面

大致题意: 给定一棵仙人掌,问有多少种重新编号的方式,使得到的仙人掌与原仙人掌同构。

圆方树

看到这种题,自然要请出静态仙人掌的大杀器——圆方树

考虑我们把仙人掌变成圆方树,则有一个显而易见的事实:仙人掌同构等价于圆方树同构。

那么问题就变成了树的同构,然后就可以树形\(DP\)了。

树形\(DP\)

对于树的同构问题,我们肯定要找出其重心,作为树的根。(若有两个重心,则新建一个根节点,断开两个重心的边并向新根连边)

\(f_x\)表示\(x\)子树内同构个数,则初始化\(f_x=\prod f_{son}\)

接下来就是大分类讨论:

  • 圆点:对于每种一样的子树(可以用\(Hash\)判),可以任意交换,累乘上个数的阶乘即可。
  • 方点(非根节点):圆方树一个基本性质,方点周围的圆点是有序的!又由于其中一个圆点是该方点父节点,所以只有以这个点为中心翻转一种方式可能使其重构,判一下即可。
  • 方点(根节点):根节点的重构方式就要复杂一些。一方面,它同样可以翻转,但却能以任一节点为中心翻转;另一方面,它还可以旋转。具体的判断方式就详见代码吧。

还有一个细节问题,就是\(Hash\)时需要给圆点方点定义不同参数,相信这还是挺显然的。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define M 1000000
#define X 1000000003
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[2*M+5];
class RoundSquareTree//圆方树
{
	private:
		#define SZ (N<<1)
		int ee,lnk[SZ+5],rt,rtt,Sz[SZ+5],Mx[SZ+5],f[SZ+5];edge e[2*SZ+5];
		struct Hash//哈希
		{
			#define ull unsigned long long
			#define RU Reg ull
			#define CU Con ull&
			ull x,y;I Hash() {x=y=0;};I Hash(CU a) {x=y=a;}I Hash(CU a,CU b):x(a),y(b){}
			I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
			I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
			I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
			I bool operator < (Con Hash& o) Con {return x^o.x?x<o.x:y<o.y;}
			I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
		}seed[2],tag[2],h[N+5],s[2*N+5],tmp[N+5];
		I void GetRt(CI x,CI lst=0)//求重心
		{
			Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
				(GetRt(e[i].to,x),Sz[x]+=Sz[e[i].to],Gmax(Mx[x],Sz[e[i].to]));
			Gmax(Mx[x],n+Pt-Sz[x]),Mx[x]^Mx[rt]?Mx[x]<Mx[rt]&&(rt=x,rtt=0):(rtt=x);
		}
		I void Cut(CI x,CI y)//把x指向y的边改为指向新根
		{
			for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to==y) return (void)(e[i].to=0);
		}
		I int DP(CI x,CI lst=0)//树形DP
		{
			RI i,j,t=0;for(f[x]=1,i=lnk[x];i;i=e[i].nxt)
				e[i].to^lst&&(f[x]=1LL*f[x]*DP(e[i].to,x)%X);//统计子节点f的乘积
			for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(s[++t]=h[e[i].to],0);//存下子节点哈希值
			if(x<=n)//分类讨论,对于圆点
			{
				sort(s+1,s+t+1);//排序
				for(i=1;i<=t;i=j+1) {j=i;W(j^t&&s[i]==s[j+1]) ++j,f[x]=1LL*f[x]*(j-i+1)%X;}//乘上同种个数的阶乘
				for(h[x]=t+1,i=1;i<=t;++i) h[x]=h[x]*seed[0]+s[i];h[x]=h[x]*tag[0]+1;//计算当前点哈希值
			}
			else if(lst)//对于非根节点的方点
			{
				RI k=0,p=1;for(i=1;i<=t;++i) s[t+i]=s[i];
				for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst) ++k;else break;//找到父节点位置
				for(i=1;i<=t;++i) tmp[t-i+1]=s[i]=s[i+k];//tmp记下翻转后的数组
				for(i=1;i<=t&&s[i]==tmp[i];++i);if(i>t) f[x]=2LL*f[x]%X;//如果翻转后一样乘2
				else if(tmp[i]<s[i]) for(i=1;i<=t;++i) s[i]=tmp[i];//取较小的表示方式
				for(h[x]=t+1,i=1;i<=t;++i) h[x]=h[x]*seed[1]+s[i];h[x]=h[x]*tag[1]+1;//计算当前点哈希值
			}return f[x];
		}
		I int SolveRt(CI x)//对于根节点的方点
		{
			RI i,t=0;for(i=lnk[x];i;i=e[i].nxt) s[++t]=h[e[i].to];for(i=1;i<=t;++i) s[t+i]=s[i];
			RI j,p=0;for(i=1;i<=t;++i) {for(j=1;j<=t&&s[j]==s[i+j-1];++j);p+=j>t;}//旋转
			for(i=1;i<=t;++i) {for(j=1;j<=t&&s[t-j+1]==s[i+j-1];++j);p+=j>t;}return p;//翻转
		}
	public:
		I RoundSquareTree()//初始化哈希参数
		{
			seed[0]=Hash(20050413,2501528028),seed[1]=Hash(20050521,302627441),
			tag[0]=Hash(233333,23333333),tag[1]=Hash(66666666,88888888);
		}
		int Pt;I void Add(CI x,CI y) {add(x,y),add(y,x);}I void Solve()
		{
			Mx[rt=0]=1e9,GetRt(1),//找重心
			rtt&&(++Pt,Cut(rt,rtt),Cut(rtt,rt),add(0,rt),add(0,rtt),rt=0),//若有两个重心新建一个根
			printf("%d",1LL*DP(rt)*(rt>n?SolveRt(rt):1)%X);//DP
		}
}RST;
namespace RSTBuilder//Tarjan建圆方树
{
	int d,dfn[N+5],low[N+5],f[N+5];
	I void Build(CI x,RI y) {++RST.Pt;W(RST.Add(y,n+RST.Pt),y^x) y=f[y];}
	I void Tarjan(CI x=1)
	{
		RI i,y;for(dfn[x]=low[x]=++d,i=lnk[x];i;i=e[i].nxt) (y=e[i].to)^f[x]&&
		(
			dfn[y]?Gmin(low[x],dfn[y]):(f[y]=x,Tarjan(y),Gmin(low[x],low[e[i].to])),
			low[y]>dfn[x]&&(RST.Add(x,y),0)
		);
		for(i=lnk[x];i;i=e[i].nxt) dfn[y=e[i].to]>dfn[x]&&f[y]^x&&(Build(x,y),0);
	}
}
int main()
{
	RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	return RSTBuilder::Tarjan(),RST.Solve(),0;
}
posted @ 2020-06-08 10:15  TheLostWeak  阅读(268)  评论(0编辑  收藏  举报