本文一样发布于我的Luogu博客,点击https://yuwenzhou-sakana-chn.blog.luogu.org/solution-p1407以获得更好的markdown题解
关于这道题,我首先想说的是:
题面过于鬼畜
具体怎么鬼畜、有多鬼畜、哪里鬼畜详见讨论区(笑到头掉)!
这题的大众做法大家都会(指来指去的),但是从讨论区看出大家好像并不是很能理解……
那就让我们来分析一下这个大众做法吧!
一、究竟谁指谁
按照题解区的主流,一句话总结就是:
情人男指女,夫妻女指男
可是,真的是这样吗? 我用事实告诉你,不然!(反着指AC实例)
其实这个容易看出,因为根据著名的Kosaraju算法的原理,一个图反向连边的强连通分量和原图是一样的。所以,只要两次连的边方向不一样就行了
而这样指又有什么用呢?让我们来分析一下样例:
二、指来指去的用处
首先鉴于题目的名字实在是没有什么意思,我们仿照样例重新画一张图:
这里,我们请出了日本著名文学家横滨港口著名侦探和黑手党: 太宰(dazai)治、中原(Nakahara)中也、芥川(Ryunosuke)龙之介、中岛(Nakajima)敦!
你告诉我性别有问题?当然是因为这样你们就不会关心男男女女的了就是没有问题!
在这张图中我们用紫色线表示共事关系,用蓝色线表示曾经有过关系。A组的成员表示主要负责人,B组的成员表示助手。(为什么是共事关系?当然是因为怕你们想多了原题过于鬼畜)
我们把“指向”定义为一种“投靠关系”,就是如果有任务的话这个人会去找谁。
首先我们让所有搭档都闹翻。我们假定关系破裂都是因为主要负责人叛变导致的,那么按照题意他会去找曾经有过关系的非搭档的人,而不知道情况的助手领到任务只会去找他的主要负责人。如下图:
或者下图:
这时关系稳定的标志就是:有一个任务出现的时候,不论是谁领到的,如果不找原先的搭档,就不能找到人了。
那么为什么会形成一个强连通分量呢?
首先我们来看环的情况。把上图的点重新布局一下,容易得到这样一个环形:
每个人只要沿着环找下去,总能找到一个人共事的。因为将曾经的关系当做等价的线条考虑进去了,所以肯定有人没有找自己现在的搭档。
当图不再是环的时候呢?
我们再引入两个点:森(Mori)欧外和尾崎(Ozaki)红叶(就是莫得图),然后乱加一些关系:
此时我们可以得到这样一个强连通分量:
那这样的强连通分量为什么是不稳定的关系呢?因为强连通分量的点两两可达,所以不存在一个人孤苦无依的情况。
这样,只要沿着“指向”的箭头寻找,每个人一定能找到一个空的伙伴和他搭档。(因为两两互达的缘故,就算是一开始找错人,这个步骤重复一定次数肯定能安排上掉的!)
这时如果双方都在一个强连通分量里面,那么他们一定可以找到NTR换搭档的方法。(因为搭档双方是不相互指的)
这个时候双向边就明显不适应了(会使得“指向”关系不符合我们的预期),所以只能是单向边。
三、口胡实现方法
理解了这个,究竟谁叛变不叛变已经无所谓了,因为明明拆了重组也很好吃啊这个代码已经可以写了。
我们只要给不同的强连通分量染色,然后看看夫妻双方在不在同一个强连通分量里面(因为这样就是戴绿帽子了)就好。
存图可以考虑用map提供的hash对应字符串和标号,当然采用其他的办法对应也可以。这里因为懒就采用大众的map做法了。
AC code(特地安排了反着指的):
#include<iostream> #include<map> using namespace std; #define N 8010 #define M 40010 struct line { int to,next; }ed[M]; map <string,int> a;//这里 int dfn[N],low[N],head[N],cnt,tot,n,m; bool ins[N]; int s[N],top,color[N],sum,deep; void addline(int u,int v) { ed[++cnt].to=v; ed[cnt].next=head[u]; head[u]=cnt; } void tarjan(int pos)//板子 { dfn[pos]=++deep; low[pos]=deep; ins[pos]=1; s[++top]=pos; for(int i=head[pos];~i;i=ed[i].next) { int y=ed[i].to; if(!dfn[y]) { tarjan(y); low[pos]=min(low[pos],low[y]); } else { if(ins[y]) low[pos]=min(low[pos],low[y]); } } if(dfn[pos]==low[pos]) { color[pos]=++sum; ins[pos]=0; while(s[top]!=pos) { color[s[top]]=sum; ins[s[top]]=0; top--; } top--; } } int main() { cin>>n; for(int i=1;i<=n+n;i++) head[i]=-1; string s1,s2; for(int i=1;i<=n+n;i+=2)//女单男双 { cin>>s1>>s2; a[s1]=i; a[s2]=i+1; addline(i+1,i); } cin>>m; for(int i=1;i<=m;i++) { cin>>s1>>s2; addline(a[s1],a[s2]); } for(int i=1;i<=n+n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n+n;i+=2)//分析婚姻 { if(color[i]==color[i+1])//可以戴绿帽 cout<<"Unsafe"<<endl; else cout<<"Safe"<<endl; } return 0; }
最后,因为本蒟蒻太菜,欢迎摘虫和hack!