题解——P2341 [HAOI2006]受欢迎的牛 tarjan缩点 + 拓扑排序

时间:2023-02-10 20:25:44

~~~题面~~~

题解:

首先tarjan缩点应该还是容易想到的,因为喜爱具有传递性,所以一个强联通分量里面的点实际上是全部等效的,所以我们可以缩成一个方便判断,

缩完点之后整张图就变成了一个有向无环图,这时我们的目标是要找到所有的可以被所有节点到达的节点(此处的节点已经是缩完后的了)

你可能会注意到“所有的”被划去了,为什么呢?

  其实这是一个非常妙的性质,因为这样的节点最多只会有一个。

why?

这就是缩点的妙处所在了,我们注意到缩点之后是没有环的。

现在我们假设我们找到了一个合法的节点,我们称之为明星节点。

那么它将有什么性质?

我们已经知道,图上所有节点都可以到达它,这意味着什么?

一旦这个节点有出度,这个出度必然连向图上的某个点x,而这个点x可以到达这个明星节点,于是我们将得到一个环。

但这和我们的前提是不符的,因为我们已经缩过点了,这是一个有向无环图,因此这是不可能出现的。

于是我们就得到了一条非常妙的性质,即明星节点必然无出度。

因此我们要找明星节点,直接找图上没有出度的那个节点就OK了。

但还必须符合一个条件,那就是这个图必须联通,

为了方便判断这一点,我们缩点后的重新建图时,我们反向连边,

这样明星节点就是没有入度的那个节点,于是我们可以顺着这个节点遍历整张图,

最后我们枚举一个是不是每个节点都被遍历到就可以判断图是否联通了。

当然这时你有可能注意到我并没有解释为什么明星节点只会有一个,其实转化到这一步这个结论就很清晰了。

假设我们现在有两个明星节点,分别是x,y,那么这意味着什么?

x和y都没有入度,,,那请问x如何才能到y呢?那么显然这两个所谓的明星节点就都是不合法的了。

这种情况可以被包括在上一种情况当中(代码实现上),因为我们可以只从第一个遇到的入度为0的节点开始遍历,

遍历完就直接判断+输出了,因为这时遍历不完还有可能是有另一个入口(即两个都是伪明星节点),这就是第二种情况了

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define getchar() *o++
  5 #define AC 101000
  6 #define ac 1200000
  7 char READ[5000100],*o=READ;
  8 /*所有点可以到一个点,就是这个点可以到其他所有点,
  9 所以要反向建边?但是感觉DP的时候又不能是反向边,,,
 10 先缩点,然后拓扑排序,再按照拓扑序顺序更新,
 11 可以到达所有块的联通块,块内的奶牛就都是明星奶牛
 12 所以复杂度nm???
 13 开bitset记录f[i]的到达情况,
 14 
 15 好吧其实有一种非常妙的做法,
 16 因为缩完点之后,就变成了有向无环图,
 17 那么如果一个联通块是明星联通块的话,它必然没有出度,
 18 因为所有点都可以到它,如果它还可以到其他点,那就成环了,
 19 而且这样的点必然只有一个,因为如果有两个点(或以上)没有出度,
 20 假如这两个点是a,b,那既然a,b都没有出度,a,b必然不能互达,
 21 所以先缩点,然后拓扑排序统计出度(特判若图不连通 or 出度为0的点大于等于2那就不合法),
 22 不过貌似可以这样:
 23 缩点后反向建边,找到第一个入度为0的点开始拓扑,如果这次拓扑没有遇到所有的点,
 24 那就代表图不联通 or 入度为0的点不止一个
 25 */
 26 int n, m, cnt, tt;
 27 int dfn[AC], low[AC], belong[AC], point[AC], in[AC], num[AC];
 28 int date[ac], Next[ac], Head[AC], tot;
 29 int date1[ac], Next1[ac], Head1[AC], tot1;
 30 int q[AC], head, tail;
 31 int s[AC], top;
 32 bool z[AC], vis[AC];
 33 inline int read()
 34 {
 35     int x = 0; char c = getchar();
 36     while(c > '9' || c < '0') c = getchar();
 37     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
 38     return x;
 39 }
 40 
 41 inline void add(int f, int w)
 42 {
 43     date[++tot] = w, Next[tot] = Head[f], Head[f] = tot;
 44 }
 45 
 46 inline void add2(int f, int w)
 47 {
 48     date1[++tot1] = w, Next1[tot1] = Head1[f], Head1[f] = tot1;
 49     ++in[w];
 50 }
 51 
 52 void pre()
 53 {
 54     int a,b;
 55     n = read(), m = read();
 56     for(R i = 1;i <= m; i++) 
 57     {
 58         a = read(), b = read();
 59         add(a, b);
 60     }
 61 }
 62 
 63 void tarjan(int x)
 64 {
 65     int now;
 66     dfn[x] = low[x] = ++tt;
 67     s[++top] = x, z[x] = true;//标记入栈
 68     for(R i = Head[x]; i ; i = Next[i])
 69     {
 70         now = date[i];
 71         if(!dfn[now]) 
 72         {
 73             tarjan(now);
 74             low[x] = min(low[x], low[now]);
 75         }
 76         else if(z[now])//如果已经访问过而且还未出栈
 77             low[x] = min(low[x], low[now]);
 78     }
 79     if(dfn[x] == low[x])
 80     {
 81         int b = ++cnt;//新建联通块
 82         while(now = s[top--])
 83         {
 84             belong[now] = b;//貌似这里只需要标记就可以了
 85             ++num[b];//记录联通块中有几个点
 86             z[now]=false;//error要标记出栈啊啊啊啊啊啊啊啊
 87             if(now == x) break;//加完自己就退出
 88         }
 89     }
 90 }
 91 
 92 #define add add2
 93 void build()
 94 {
 95     int x;
 96     for(R i = 1; i <= n; i++)
 97     {
 98         for(R j = Head[i]; j ; j = Next[j])
 99         {
100             x = date[j];
101             if(belong[i] != belong[x]) add(belong[x], belong[i]);
102         } 
103     }
104 }
105 #undef add
106 #define date date1
107 #define Next Next1
108 #define Head Head1
109 #define tot tot1
110 
111 void topsort(int x)
112 {
113     int now;
114     q[++tail] = x;
115     while(head < tail)
116     {
117         x = q[++head];
118         vis[x] = true;
119         for(R i = Head[x]; i ; i = Next[i])
120         {
121             now = date[i];
122             if(!(--in[now])) q[++tail] = now;
123         }
124     }
125 }
126 
127 void work()
128 {
129     bool done = true;
130     for(R i = 1; i <= cnt; i++)
131     {
132         if(!in[i]) 
133         {
134             topsort(i);
135             for(R j = 1; j <= cnt; j++)
136                 if(!vis[j]) done = false;//error!!!是vis[j]啊啊啊啊啊
137             if(done) printf("%d\n", num[i]);
138             else printf("0\n");
139             break;
140         }
141     }
142 }
143 
144 int main()
145 {
146 //    freopen("in.in","r",stdin);
147     fread(READ,1,5000000,stdin);
148     pre();
149     for(R i=1;i<=n;i++) 
150         if(!dfn[i]) tarjan(i);
151     build();
152     work();
153 //    fclose(stdin);
154     return 0;
155 }