考虑建出圆方树。显然只有同一个点相连的某些子树同构会产生贡献。以重心为根后(若有两个任取一个即可),就只需要处理子树内部了。

  如果子树的根是圆点,其相连的同构子树可以任意交换,方案数乘上同构子树数量的阶乘即可。而若是方点,注意到其相邻的圆点在原树中是有序地在一个环上的,要产生同构只能旋转或翻转该环。并且因为一开始我们选择了重心为根,所以对于非重心的方点,将其所在的环旋转显然是无法产生贡献的。所以对于方点的所有孩子按环上顺序存储,其哈希值应以该顺序计算,正反取较小的,算贡献时对非重心点只考虑翻转,重心特判一下。判同构当然采取哈希。

  调了一年发现只有点双写错了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define N 2010
#define P 1000000003
#define M 10010
#define ull unsigned long long
#define p1 509
#define p2 923
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<''||c>'')) c=getchar();return c;}
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
int n,m,p[N],t,dfn[N],low[N],stk[N],top,cnt,tot,ans=;
vector<int> BCC[N];
struct data{int to,nxt;
}edge[M];
void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;}
void tarjan(int k)
{
dfn[k]=low[k]=++cnt;
stk[++top]=k;
for (int i=p[k];i;i=edge[i].nxt)
if (dfn[edge[i].to]) low[k]=min(low[k],dfn[edge[i].to]);
else
{
tarjan(edge[i].to);
low[k]=min(low[k],low[edge[i].to]);
if (low[edge[i].to]>=dfn[k])
{
tot++;
while (stk[top]!=edge[i].to) BCC[tot].push_back(stk[top]),top--;
BCC[tot].push_back(edge[i].to);top--;
BCC[tot].push_back(k);
}
}
}
namespace blocktree
{
int p[N],t,size[N];
ull hash[N],a[N];
struct data{int to,nxt;}edge[M];
void addedge(int x,int y)
{
//cout<<x<<' '<<y<<endl;
t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;
t++;edge[t].to=x,edge[t].nxt=p[y],p[y]=t;
}
void make(int k,int from)
{
size[k]=;
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=from)
{
make(edge[i].to,k);
size[k]+=size[edge[i].to];
}
}
int findroot(int k,int from,int s)
{
int mx=;
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=from&&size[edge[i].to]>size[mx]) mx=edge[i].to;
if ((size[mx]<<)>s) return findroot(mx,k,s);
else return k;
}
void dfs(int k,int from)
{
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=from) dfs(edge[i].to,k);
if (k<=n)
{
int cnt=;
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=from) a[++cnt]=hash[edge[i].to];
sort(a+,a+cnt+);
for (int i=;i<=cnt;i++) hash[k]=hash[k]*p1+a[i];
hash[k]=hash[k]*p2+size[k];
for (int i=;i<=cnt;i++)
{
int t=i;
while (t<cnt&&a[t+]==a[i]) t++;
int fac=;
for (int j=;j<=t-i+;j++) fac=1ll*fac*j%P;
ans=1ll*ans*fac%P;
i=t;
}
}
else if (k!=from)
{
if (BCC[k-n].size()>)
{
int pos;
for (int i=;i<BCC[k-n].size();i++)
if (BCC[k-n][i]==from) {pos=i;break;}
ull hash1=,hash2=;int x=pos,y=pos;
for (int i=;i<BCC[k-n].size();i++)
{
x--;if (x<) x+=BCC[k-n].size();
hash1=hash1*p1+hash[BCC[k-n][x]];
y++;if (y==BCC[k-n].size()) y=;
hash2=hash2*p1+hash[BCC[k-n][y]];
}
if (hash1==hash2) ans=2ll*ans%P;
hash[k]=min(hash1,hash2)*p2+size[k];
}
else for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=from) hash[k]=hash[edge[i].to]*p2+size[k];
}
else
{
ull hash1=;
for (int i=;i<BCC[k-n].size();i++)
hash1=hash1*p1+hash[BCC[k-n][i]];
int cnt=;
for (int i=;i<BCC[k-n].size();i++)
{
int x=i,y=i;ull h1=,h2=;
for (int j=;j<BCC[k-n].size();j++)
{
h1=h1*p1+hash[BCC[k-n][x]];
h2=h2*p1+hash[BCC[k-n][y]];
x++;if (x==BCC[k-n].size()) x=;
y--;if (y<) y+=BCC[k-n].size();
}
if (h1==hash1) cnt++;
if (h2==hash1) cnt++;
}
if (BCC[k-n].size()==) cnt/=;
ans=1ll*ans*cnt%P;
}
}
void solve()
{
make(,);
int root=findroot(,,size[]);
make(root,root);
dfs(root,root);
//cout<<root<<endl;
//for (int i=1;i<=19;i++) cout<<size[i]<<' '<<hash[i]<<endl;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj3899.in","r",stdin);
freopen("bzoj3899.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read(),m=read();
for (int i=;i<=m;i++)
{
int x=read(),y=read();
addedge(x,y),addedge(y,x);
}
tarjan();
for (int i=;i<=tot;i++)
for (int j=;j<BCC[i].size();j++)
blocktree::addedge(n+i,BCC[i][j]);
blocktree::solve();
cout<<ans;
return ;
}

BZOJ3899 仙人掌树的同构(圆方树+哈希)的更多相关文章

  1. 【NOI2013模拟】坑带的树(仙人球的同构+圆方树乱搞+计数+HASH)

    [NOI2013模拟]坑带的树 题意: 求\(n\)个点,\(m\)条边的同构仙人球个数. \(n\le 1000\) 这是一道怎么看怎么不可做的题. 这种题,肯定是圆方树啦~ 好,那么首先转为广义圆 ...

  2. UOJ#23. 【UR #1】跳蚤国王下江南 仙人掌 Tarjan 点双 圆方树 点分治 多项式 FFT

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ23.html 题目传送门 - UOJ#23 题意 给定一个有 n 个节点的仙人掌(可能有重边). 对于所有 ...

  3. 仙人掌&圆方树学习笔记

    仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...

  4. 仙人掌 && 圆方树 && 虚树 总结

    仙人掌 && 圆方树 && 虚树 总结 Part1 仙人掌 定义 仙人掌是满足以下两个限制的图: 图完全联通. 不存在一条边处在两个环中. 其中第二个限制让仙人掌的题做 ...

  5. UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)

    题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...

  6. 圆方树总结 [uoj30]Tourists

    圆方树总结 所谓圆方树就是把一张图变成一棵树. 怎么变啊qaq 这里盗一张图 简单来说就是给每一个点双新建一个点,然后连向这个点双中的每一个点.特殊的,把两个点互相连通的也视作一个点双. 我们把原来就 ...

  7. [JZOJ 5909] [NOIP2018模拟10.16] 跑商(paoshang) 解题报告 (圆方树)

    题目链接: https://jzoj.net/senior/#contest/show/2529/2 题目: 题目背景:尊者神高达很穷,所以他需要跑商来赚钱题目描述:基三的地图可以看做 n 个城市,m ...

  8. 【题解】Uoj#30 Tourist(广义圆方树+树上全家桶)

    [题解]Uoj#30 Tourist(广义圆方树+树上全家桶) 名字听起来很霸气其实算法很简单.... 仙人掌上的普通圆方树是普及题,但是广义圆方树虽然很直观但是有很多地方值得深思 说一下算法的流程: ...

  9. Note -「圆方树」学习笔记

    目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...

  10. Codeforces 487E Tourists [广义圆方树,树链剖分,线段树]

    洛谷 Codeforces 思路 首先要莫名其妙地想到圆方树. 建起圆方树后,令方点的权值是双联通分量中的最小值,那么\((u,v)\)的答案就是路径\((u,v)\)上的最小值. 然而这题还有修改, ...

随机推荐

  1. QT 字符串的使用技巧总结

    QT 的字符串的 使用的总结. 1.字符串截取函数的使用 QString str; QString csv = "forename,middlename,surname,phone" ...

  2. Android学习之基础知识四-Activity活动6讲(体验Activity的生命周期)

    一.体验活动的生命周期的执行 代码组成: 1.三个Java类:MainActivity.java.NormalActivity.java.DialogActivity.java 2.三个布局文件:ac ...

  3. Windows 系统安装Docker Compose 步骤

    参考 Docker Compose official 官方安装指南: https://docs.docker.com/compose/install/ 实际上到目前为止还不能直接在Windows上安装 ...

  4. Luogu4609 FJOI2016 建筑师 第一类斯特林数

    题目传送门 题意:给出$N$个高度从$1$到$N$的建筑,问有多少种从左往右摆放这些建筑的方法,使得从左往右看能看到$A$个建筑,从右往左看能看到$B$个建筑.$N \leq 5 \times 10^ ...

  5. 移动端高清适配方案(解决图片模糊问题、1px细线问题)

    本文介绍了移动端适配的3种方法,以及移动端图片模糊问题和1px细线问题的解决方法.当然了,在这之前先整理了与这些方法相关的知识:物理像素.设备独立像素.设备像素比和viewport. >> ...

  6. Luogu P2421 [NOI2002]荒岛野人

    最近上课时提到的一道扩欧水题.还是很可做的. 我们首先注意到,如果一个数\(s\)是符合要求的,那么那些比它大(or 小)的数不一定符合要求. 因此说,答案没有单调性,因此不能二分. 然后题目中也提到 ...

  7. cython学习

    学习网址:http://blog.csdn.net/i2cbus/article/details/23791309

  8. Python从菜鸟到高手(3):声明变量

    变量(variable)是Python语言中一个非常重要的概念.变量的主要作用就是为Python程序中的某个值起一个名字.类似于"张三"."李四"." ...

  9. easyui datagrid remoteSort的实现 Controllers编写动态的Lambda表达式 IQueryable OrderBy扩展

    EF 结合easy-ui datagrid 实现页面端排序 EF动态编写排序Lambda表达式 1.前端页面 var mainListHeight = $(window).height() - 20; ...

  10. Linux下分布式系统以及CAP理论分析

    CAP理论被很多人拿来作为分布式系统设计的金律,然而感觉大家对CAP这三个属性的认识却存在不少误区,那么什么是CAP理论呢?CAP原本是一个猜想,2000年PODC大会的时候大牛Brewer提出的,他 ...