[USACO08DEC]Trick or Treat on the Farm
作者:互联网
这道题有一个特别重要的一点,就是节点数为 n 的图只有 n 条边,于是就有一下几个性质:
1.每一个点的出度都为1。
2.一个k个节点的强连通分量都是有k条边的环,而且这个环不会通往其他的点,只可能有别的点通往这个环。
所以说,对于一个在环中的点,答案就是这个环的节点数(包括自环),对于一个不在环中的点,答案就是通往环的距离+环的节点数。
因此,首先用tarjan缩点,然后反向建立缩点后的图。为什么要反向建呢?先考虑对于上文第二种情况,暴力的做法就是每一次遇到这样的一个点,就暴力的一步一步找,直到找到环为止。然而如果整张图只有一个自环,时间复杂度就会退化到O(n2)。因此要想办法维护每一个环外的点到环的距离。如果我们反向建边,就相当于求每一个环到环外点的距离,而且因为环外点之间都只有一条边,因此路径是唯一的,所以只要bfs一次就能求出通往这个环的点的距离。
还有几点要注意:
1.就是上文求出的距离的节点编号都是缩点后的编号,和原图编号不一样,因此我们要建立一个从新图编号到原图编号的映射。又因为这个映射是一对多,所以还得开一个vector存。这个在tarjan的时候就可以顺便处理出来。
2.缩点后,自环和单点是没有区别的,所以必须先把是自环的点标记一下。代码中又开了一个vector,把是自环的点都存了下来(不知有没有更好的方法)。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #include<cstdlib> 7 #include<vector> 8 #include<queue> 9 #include<stack> 10 #include<cctype> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a) memset(a, 0, sizeof(a)) 15 typedef long long ll; 16 typedef double db; 17 const int INF = 0x3f3f3f3f; 18 const db eps = 1e-8; 19 const int maxn = 1e5 + 5; 20 inline ll read() 21 { 22 ll ans = 0; 23 char ch = getchar(), last = ' '; 24 while(!isdigit(ch)) last = ch, ch = getchar(); 25 while(isdigit(ch)) ans = (ans << 3) + (ans << 1) + ch - '0', ch = getchar(); 26 if(last == '-') ans = -ans; 27 return ans; 28 } 29 inline void write(ll x) 30 { 31 if(x < 0) putchar('-'), x = -x; 32 if(x >= 10) write(x / 10); 33 putchar(x % 10 + '0'); 34 } 35 36 int n; 37 vector<int> v[maxn]; 38 39 int dfn[maxn], low[maxn], cnt = 0; 40 bool in[maxn]; 41 stack<int> st; 42 int col[maxn], val[maxn], ccol = 0; 43 vector<int> pos[maxn]; //映射 44 void tarjan(int now) 45 { 46 dfn[now] = low[now] = ++cnt; 47 st.push(now); in[now] = 1; 48 for(int i = 0; i < (int)v[now].size(); ++i) 49 { 50 if(!dfn[v[now][i]]) 51 { 52 tarjan(v[now][i]); 53 low[now] = min(low[now], low[v[now][i]]); 54 } 55 else if(in[v[now][i]]) low[now] = min(low[now], dfn[v[now][i]]); 56 } 57 if(low[now] == dfn[now]) 58 { 59 int x; ccol++; 60 do 61 { 62 x = st.top(); 63 in[x] = 0; st.pop(); 64 col[x] = ccol; 65 pos[ccol].push_back(x); 66 val[ccol]++; 67 }while(x != now); 68 } 69 return; 70 } 71 72 vector<int> v2[maxn]; //新图 73 void newGraph(int now) 74 { 75 for(int i = 0; i < (int)v[now].size(); ++i) 76 { 77 int x = col[now], y = col[v[now][i]]; 78 if(x == y) continue; 79 v2[y].push_back(x); //反向建边 80 } 81 } 82 83 vector<int> ccir; //储存是自环的点 84 int ans[maxn], ans2[maxn]; //ans[]是新图的节点编号的答案,ans2[]是原图的 85 86 void bfs(int s) 87 { 88 ans[s] = val[s]; 89 queue<int> q; 90 q.push(s); 91 while(!q.empty()) 92 { 93 int now = q.front(); q.pop(); 94 for(int i = 0; i < v2[now].size(); ++i) 95 { 96 ans[v2[now][i]] = ans[now] + 1; 97 q.push(v2[now][i]); 98 } 99 } 100 } 101 102 int main() 103 { 104 n = read(); 105 for(int i = 1; i <= n; ++i) 106 { 107 int x = read(); 108 v[i].push_back(x); 109 if(i == x) ccir.push_back(i); //该点是自环 110 } 111 for(int i = 1; i <= n; ++i) if(!dfn[i]) tarjan(i); 112 for(int i = 1; i <= n; ++i) newGraph(i); 113 for(int i = 0; i < (int)ccir.size(); ++i) ans[col[ccir[i]]] = -1; //把是自环的点在新图上标记一下 114 for(int i = 1; i <= ccol; ++i) if(val[i] > 1 || ans[i] == -1) bfs(i); 115 //如果这个点是一个环(包括自环),就维护所有能到达他的点的距离 116 for(int i = 1; i <= ccol; ++i) 117 for(int j = 0; j < (int)pos[i].size(); ++j) ans2[pos[i][j]] = ans[i]; //再将答案映射到原图上 118 for(int i = 1; i <= n; ++i) write(ans2[i]), enter; 119 return 0; 120 }View Code
标签:include,Farm,Trick,int,maxn,Treat,ans,now,low 来源: https://blog.51cto.com/u_15234622/2830902