zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    Android编译系统环境过程初始化分析【转】
    Android内核开发:理解和掌握repo工具【转】
    QQ空间如何设置被删除的好友不能访问空间
    用简单的C语言实现多任务轮流切换(模拟操作系统线程机制)【转】
    可重入函数与不可重入函数【转】
    关于链表中头指针和头结点的理解【转】
    C语言中static的使用方法【转】
    指针与地址的区别【转】
    柔性数组【转】
    void及void指针介绍【转】
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3899.html
Copyright © 2011-2022 走看看