【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;
}
待到再迷茫时回头望,所有脚印会发出光芒