P4672 [BalticOI 2011]-小清新乱搞
P4672 [BalticOI 2011] Tree Mirroring (Day2)题解
评价:小清新乱搞
第一道(应该是)独立切的黑题(尽管你谷的黑具体难度一直很随机)
建议降蓝
在这里感谢一下我的好朋友@mahaorui2012,他给我推荐了这道题,并和我一起证明了乱搞随机化做法。
这里不提供正解做法。正解是一个线性的确定性算法,详情可见其他题解。
题意
对于一棵树 \(T\),并复制一棵与 \(T\) 同构的树 \(S\)。构造一个新的图 \(T'\),新图 \(T'\) 通过合并 \(T\) 和 \(S\) 中相应的非根的叶节点得到。我们称这样的图为树之镜像图。
给定一个图 \(G\),你需要判断 \(G\) 是否是树之镜像图。
点数 \(n\),边数 \(m\)。
$ n\le 10^5 $
样例三:
22 28
13 8
8 1
1 22
1 12
1 14
13 18
13 4
4 20
20 7
13 15
15 3
15 9
9 16
9 19
22 5
12 5
14 5
5 11
11 6
18 6
7 10
10 17
17 6
3 21
21 6
16 2
19 2
2 21
YES
做法分析
我为什么要给出样例 3 呢?好难猜啊。让我们手玩一下:

思考一下我们是怎么做的:考虑对称性,肯定每次找一个环,把它相对的两个点作为叶子,然后多试几遍就出来了。深入思考一下,这启发我们,如果有两个环的并是一个路径,那么这个路径的中点一定是叶子。
与此同时,根据手玩的过程,也不难观察到,由于对称性,以一个叶子为起始进行 bfs,如果一个点有两条不相同的最短路,那么这个点也一定是叶子。如果知道了所有叶子,那么随便一个树哈希什么的就是简单的了。并非简单,调了三百年。
这个东西是一个 \(O(n)\) 的判定。
那么,如何找到一个叶子呢?如果旁边有人看了眼题解并说了一个名词,不难想到,叶子,一定是一个极长的中间点度数均为 \(2\) 的链的中点。如图中的 $6 \rightarrow 17 \rightarrow 10 \rightarrow 7 \rightarrow 20 \rightarrow 4 \rightarrow 13 $,其中点为 \(7\)。
单这一点只有必要性,没有充分性啊。比如说图中的 \(6 \rightarrow 11 \rightarrow 5\),这条链的中点 \(11\) 就不是一个叶子。
但是,注意到,一个极长链至少合并了一个叶子,也就是 \(O(leaf)\) 条的,而包含叶子的极长链显然也是 $ O(leaf) $ 的。树有两颗,所以包含叶子的极长链数量应该在 $ \frac{1}{3} $ 量级。
这非常启示我们乱搞随机化。每次找一条链并将其中点设为叶子,然后跑一遍判定,然后随机我们的链跑个几百次(实际上 \(200\) 次就会超时了……),并声称大概率找到,然后就做完啦!
记得特判偶环。我这里跑个 \(60\) 次就能 AC 了。
代码
理论上这个算法能处理链的情况,但是由于我实现比较唐,所以需要特判……
修复:降低枚举次数即可
类似的还有是否有超过两个点度数等于 \(1\) 的情况也是类似,不加特判会 TLE awa
修复:降低枚举次数即可
补充:理论上应该找链而非找点,否则复杂度错误。实现上,也就是找到一条链后直接把这条链上所有点给删掉(即 canguess 和 vis),但是汤比作者忘了……不过也能过。
已修复。顺便解释一下,一条链如果度数为 \(2\) 的节点数量是 \(0\),那么这条链中一定没有叶子;反之一个度数为 \(2\) 节点一定在且仅在一条链中,所以这么做不会误删没到过的链。
void ANS(bool x){if(x==0) puts("NO");else puts("YES");exit(0);}
vector <int> e[N];
vector <int> canguess;
bool vis[N];
vector <int> lik;
void find_link(int x){
lik.clear();
int last=0,next=0;
while(e[x].size()==2){
for(int i:e[x]) if(i!=last) next=i;
last=x,x=next;
}
lik.push_back(x);vis[x]=1;
swap(last,x);
lik.push_back(x);vis[x]=1;
while(e[x].size()==2){
for(int i:e[x]) if(i!=last) next=i;
last=x,x=next;lik.push_back(x);vis[x]=1;
}
}
vector <int> leaf;
queue <int> q;
int dis[N],num[N];
LL has[N];
int du[N];
bool check(int x)
{
while(!q.empty()) q.pop();//bfs 找叶子
memset(dis,0x3f,sizeof(dis));
leaf.clear();
q.push(x);dis[x]=0;num[x]=2;
while(!q.empty()){
int u=q.front();q.pop();
for(int v:e[u]){
if(dis[v]>dis[u]+1){
dis[v]=dis[u]+1;num[v]=1;
q.push(v);
}
else if(dis[v]==dis[u]+1) num[v]++;
}
}
For(i,1,n) if(dis[i]==INF) ANS(0),exit(0);//判掉不连通
For(i,1,n)
if(num[i]==2) leaf.push_back(i);
else if(num[i]>=3) return 0;
while(!q.empty()) q.pop();memset(du,0,sizeof(du));
const LL mod=1000000000000037;//树哈希部分
for(int i:leaf) has[i]=rd(1,mod-1),du[i]=-1,q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
for(int v:e[u]){
if(du[v]==-1) continue;
du[v]++;has[v]=(__int128)(has[v]==0?1:has[v])*has[u]%mod;//孩子们一定要赋初始值 QAQ
if(du[v]>=e[v].size()-1) has[v]=(has[v]+1)%mod,du[v]=-1,q.push(v);
}
}
For(i,1,n) if(has[i]==0) return 0;
for(int i:leaf) has[i]=0;
sort(has+1,has+1+n);
For(i,1,n){
if(has[i]==0) continue;
if(has[i]!=has[i+1]) return 0;//判断点权是否对称(成对)
else i+=1;
}
return 1;
}
int Test=1;
void mian()
{
read(n),read(m);
int real_node=m/2+1;
int leaves=real_node*2-n;
For(i,1,m){int u,v;read(u),read(v);e[u].push_back(v),e[v].push_back(u);}
int d1=0,d2=0,d3=0;
For(i,1,n)
if(e[i].size()==1) d1++;
else if(e[i].size()==2) d2++;
else d3++;
//if(d1==2&&d3==0) ANS(n&1);//特判链,但这个算法理论上能处理链的。
if(d1==0&&d3==0) ANS(!(n&1));//特判偶环。
//if(d1>2) ANS(0);//如果有两个度数为 1 的点就完蛋啦~不过理论上算法能处理。
For(i,1,50){
For(i,1,n) if(!vis[i]&&e[i].size()==2) canguess.push_back(i);
if(canguess.empty()) ANS(0);
int x=rd(0,canguess.size()-1);x=canguess[x];
find_link(x);//找链
if(!(lik.size()&1)) continue;//必须是奇数链,否则不存在叶节点
int leaf=lik[lik.size()>>1];
if(check(leaf)) ANS(1);
}
ANS(0);
}
参考资料
本文没有也不需要其他参考资料(我终于能写一次这句话啦)
update on 26/7/21 修复代码中错误/可优化的部分。更新了参考资料。

浙公网安备 33010602011771号