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

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

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

#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. Omi框架学习之旅 - 插件机制之omi-touch 及原理说明

    这个插件也能做好多好多的事,比如上拉下拉加载数据,轮播,等一切和运动有关的特效. 具体看我的allowTouch这篇博客,掌握了其用法,在来看它是怎么和omi结合的.就会很简单. 当然使用起来也比较方 ...

  2. SkylineGlobe 如何实现工程进度管理或者说是对象生命周期管理

    SkylineGlobe 的 TerraExplorer Pro里面,给我们提供了一个Timespan Tags工具,通过这个工具,我们可以设置ProjectTree任务组对象的生命周期: 然后通过调 ...

  3. 使用HashSet<>去除重复元素的集合

    比如,某一个阵列中,有重复的元素,我们想去除重复的,保留一个.HashSet<T>含不重复项的无序列表,从MSDN网上了解到,这集合基于散列值,插入元素的操作非常快. 你可以写一个方法: ...

  4. [转]curl的错误代码

    转贴者按: 今天在使用curl的时候碰到了一个错误,如下所示: External Program Failed: D:\Tools\curl\curl.exe (return code was 18) ...

  5. 面试2——java基础2

    11.MVC设计模型 mvc设计模型是一种使用model-view-controller(模型-视图-控制器)设计创建web应用程序的模式.是一种开发模式,好处是可以将界面和业务逻辑分离. model ...

  6. 在线排错之curl命令详解

    春回大地万物复苏,好久不来,向各位博友问好. 简介 cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行.它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下 ...

  7. 20min 快速着手Markdown

    目录 Markdown介绍和基本使用 初步介绍 markdown的使用场景 为什么是 Markdown markdown的基本语法和使用平台 Q&A: Markdown介绍和基本使用 初步介绍 ...

  8. Spring RPC 入门学习(3)-插入Student对象

    Spring RPC 向后台传递对象 1. 新建RPC接口:StudentInterface.java package com.cvicse.ump.rpc.interfaceDefine; impo ...

  9. tableView优化思路

    一般优化的思路: 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法. 复杂界面可采用异步绘制. 在大量图片展示时,可以滑动时按需加载. 尽量少用或不 ...

  10. Linux常用指令【转载】

    [收藏]Linux常用指令[转载] $ 命令行提示符 粗体表示命令 斜体表示参数 filename, file1, file2 都是文件名.有时文件名有后缀,比如file.zip command 命令 ...