Tarjan:
學習資料:傳送門
通俗易懂版:傳送門
kosaraju:
POJ 2186
题意:A认为B厉害, B认为C厉害, 则A认为C厉害, 求有多少个牛被其他n-1个牛都认为厉害
首先给定的图没有保证一定连通、所以我们开始处理是不是有多个连通块、
如果只有一个连通块那么缩点后的图就是个DAG图, 如果其中出度为0的点有且只有一个那么答案就是那个点的个数
如果存在两个,很显然那两个点是不互达的
DAG图至少存在一个入度为0的点
Tarjan算法直接求 注意是多组数据
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define REP(i, x, n) for(int i = x; i < n; ++i)
const int qq = 1e5;
const int INF = 1e9;
int fa[qq];
int dfn[qq];
int low[qq];
int Stack[qq];
int belong[qq];
int num[qq];
int deg[qq];
bool instack[qq];
int n, m, Bcnt, top, Index;
vector <int> vt[qq];
map<pill, bool> mp;
int Find(int x){
return fa[x] == -1 ? x : fa[x] = Find(fa[x]);
}
void Tarjan(int u){
low[u] = dfn[u] = ++Index;
instack[u] = true;
Stack[top++] = u;
int sz = (int)vt[u].size();
REP(i, 0, sz){
int v = vt[u][i];
if(!dfn[v]){
Tarjan(v);
low[u] = min(low[u], low[v]);
}else if(instack[v]){
low[u] = min(low[u], dfn[v]);
}
}
int v;
if(low[u] == dfn[u]){
++Bcnt;
do{
v = Stack[--top];
instack[v] = false;
belong[v] = Bcnt;
num[Bcnt]++;
}while(v != u);
}
}
int main(){
while(scanf("%d%d", &n, &m) != EOF){
memset(fa, -1, sizeof(fa));
memset(num, 0, sizeof(num));
memset(dfn, 0, sizeof(dfn));
memset(deg, 0, sizeof(deg));
memset(instack, false, sizeof(instack));
mp.clear();
for(int i = 0; i <= n; ++i)
vt[i].clear();
int a, b;
REP(i, 0, m){
scanf("%d%d", &a, &b);
if(mp[mk(a, b)]) continue;
mp[mk(a, b)] = true;
vt[a].pb(b);
int x = Find(a), y = Find(b);
if(x == y) continue;
fa[y] = x;
}
int p = 0;
for(int i = 1; i <= n; ++i)
if(fa[i] == -1) p++;
if(p > 1){
printf("0\n");
return 0;
}
Bcnt = top = Index = 0;
for(int i = 1; i <= n; ++i) {
if (!dfn[i]) Tarjan(i)
}
for(int i = 1; i <= n; ++i){
for(int j = 0; j < (int)vt[i].size(); ++j){
if(belong[i] == belong[vt[i][j]]) continue;
deg[belong[i]]++;
}
}
int cnt = 0, id;
for(int i = 1; i <= Bcnt; ++i){
if(deg[i] == 0) cnt++, id = i;
}
if(cnt == 1) printf("%d\n", num[id]);
else printf("0\n");
}
return 0;
}
POJ1236
题意:有一个图, SubA是问至少传送软件给几个学校能使的所有学校能得到软件(得到软件的学校会把软件传给它所有能达到的学校), SubB是问至少需要添加几条边能使得传送软件给任意一个学校, 其他所有学校都能得到软件。
首先SubA很容易能分析出来是求缩点后入度为0的点的个数, SubB呢? 我一开始以为是算出度为0的个数,其实不然,
基于有向无环图(DAG)的考虑,我们知道一条有向边能贡献一个入度和一个出度(不同顶点), 而我们知道强连通图的所有顶点的入度和出度都不为0, 所以我们可以这样考虑,一条边能贡献一个入度和一个出度,那么我们只要把所有入度和出度都填满了,整个图就是个强连通图,需要多少条边呢? 就是入度为0的个数 , 出度为0的个数中的最大值。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <stack>
#include <queue>
#include <string>
#include <vector>
using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define REP(i, x, n) for(int i = x; i < n; ++i)
const int qq = 100 + 10;
vector<int> v[qq], rv[qq];
int fa[qq], belong[qq], num[qq], deg[qq];
bool vis[qq];
int n, Bcnt;
stack<int> st;
int find(int x){
return fa[x] == -1 ? x : fa[x] = find(fa[x]);
}
void dfs(int u){
vis[u] = true;
REP(i, 0, (int)v[u].size())if(!vis[v[u][i]]){
dfs(v[u][i]);
}
st.push(u);
}
void rdfs(int u){
vis[u] = true;
belong[u] = Bcnt;
num[Bcnt]++;
REP(i, 0, (int)rv[u].size())if(!vis[rv[u][i]]){
rdfs(rv[u][i]);
}
}
int main(){
while(scanf("%d", &n) != EOF){
int x;
memset(fa, -1, sizeof(fa));
while(!st.empty()) st.pop();
REP(i, 0, n + 1) v[i].clear(), rv[i].clear();
REP(i, 0, n){
while(true){
scanf("%d", &x);
if(x == 0) break;
int a = find(x), b = find(i + 1);
if(a != b) fa[b] = a;
v[i + 1].pb(x);
rv[x].pb(i + 1);
}
}
int cnt = 0;
for(int i = 1; i <= n; ++i)
if(fa[i] == -1) cnt++;
memset(vis, false, sizeof(vis));
for(int i = 1; i <= n; ++i)if(!vis[i]){
dfs(i);
}
memset(vis, false, sizeof(vis));
memset(num, 0, sizeof(num));
Bcnt = 0;
for(int i = st.size() - 1; i >= 0; --i){
int u = st.top(); st.pop();
if(!vis[u]) ++Bcnt, rdfs(u);
}
if(Bcnt == 1){
printf("1\n0\n");
return 0;
}
memset(deg, 0, sizeof(deg));
int SubA = 0;
for(int i = 1; i <= n; ++i)
for(int j = 0; j < (int)v[i].size(); ++j){
if(belong[i] == belong[v[i][j]]) continue;
deg[belong[v[i][j]]]++;
}
for(int i = 1; i <= Bcnt; ++i)
if(deg[i] == 0) SubA++;
int SubB = 0;
memset(deg, 0, sizeof(deg));
for(int i = 1; i <= n; ++i)
for(int j = 0; j < (int)v[i].size(); ++j){
if(belong[i] == belong[v[i][j]]) continue;
deg[belong[i]]++;
}
for(int i = 1; i <= Bcnt; ++i)
if(deg[i] == 0) SubB++;
printf("%d\n%d\n", SubA, max(SubA, SubB));
}
return 0;
}
POJ2553
题意:给出n,m代表点的个数和边的条数, 然后a b代表a到b有一条有向边, 要统计sink点的个数。
何为sink点呢, 就是如果有一条u->到v的路径一定存在一条v-›u的路径 这样u就是sink点。
如果是一个连通图,我们知道缩点后的图是个DAG图(有向无环图)那么对于有出度的点,那么这个点一定不是sink点,DAG图是无环的。
所以答案就是统计缩点后的图中出度为0的点、是不是连通图都没关系
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <stack>
#include <queue>
#include <string>
#include <vector>
using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define REP(i, x, n) for(int i = x; i < n; ++i)
const int qq = 5005;
vector<int> vt[qq], rvt[qq], Ver[qq], order;
int belong[qq], deg[qq];
bool vis[qq];
int Scc, n, m;
stack<int> st;
void Init(){
Scc = 0;
memset(vis, false, sizeof(vis));
memset(deg, 0, sizeof(deg));
for(int i = 0; i <= n; ++i)
vt[i].clear(), rvt[i].clear(), Ver[i].clear();
order.clear();
}
void Dfs(int u){
vis[u] = true;
REP(i, 0, (int)vt[u].size())if(!vis[vt[u][i]]){
Dfs(vt[u][i]);
}
st.push(u);
}
void Rdfs(int u){
vis[u] = true;
belong[u] = Scc;
Ver[Scc].pb(u);
REP(i, 0, (int)rvt[u].size())if(!vis[rvt[u][i]]){
Rdfs(rvt[u][i]);
}
}
int main(){
while(scanf("%d", &n) != EOF){
if(n == 0) break;
Init();
scanf("%d", &m);
int a, b;
REP(i, 0, m){
scanf("%d%d", &a, &b);
vt[a].pb(b);
rvt[b].pb(a);
}
for(int i = 1; i <= n; ++i)
if(!vis[i]) Dfs(i);
memset(vis, false, sizeof(vis));
for(int i = st.size() - 1; i >= 0; --i){
int u = st.top(); st.pop();
if(!vis[u]) ++Scc, Rdfs(u);
}
for(int i = 1; i <= n; ++i)
for(int j = 0; j < (int)vt[i].size(); ++j){
int u = vt[i][j];
if(belong[i] == belong[u]) continue;
deg[belong[i]]++;
}
for(int i = 1; i <= Scc; ++i){
if(deg[i] == 0){
for(int j = 0; j < (int)Ver[i].size(); ++j)
order.pb(Ver[i][j]);
}
}
sort(order.begin(), order.end());
for(int i = 0; i < (int)order.size() - 1; ++i)
printf("%d ", order[i]);
printf("%d\n", order[(int)order.size() - 1]);
}
return 0;
}
POJ3177
题意:给定一个无向图, 问你再加多少条边能使的任意对顶点之间有两条不同的路径可以到达,Routes are considered separate if they use none of the same paths, even if they visit the same intermediate field along the way.
这是题目关于不同路径的原话。 个人觉得实际上是针对这样的数据
2 2
1 2
2 1
这数据的答案是0及我们要考虑重边的情况
对于无向图, 我们可以求出边双连通分量然后缩点, 然后根据叶子节点的数目即可求出答案
对于处理重边我们可以用一个标志变量来完成
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <utility>
using namespace std;
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define LL long long
#define REP(i, x, n) for(int i = x; i < n; ++i)
const int qq = 1e5 + 10;
int n, m;
int low[qq], dfn[qq], belong[qq], deg[qq], pre[qq], Stack[qq];
int Index, Bcnt, top;
vector<int> vt[qq];
map<pill, bool> mp;
void Tarjan(int u, int fa){
low[u] = dfn[u] = ++Index;
Stack[top++] = u;
pre[u] = fa;
int sz = (int)vt[u].size();
int k = 0;
REP(i, 0, sz){
int v = vt[u][i];
if(v == fa && !k){
++k;
continue;
}
if(!dfn[v]){
Tarjan(v, u);
low[u] = min(low[u], low[v]);
}else{
low[u] = min(low[u], dfn[v]);
}
}
int v;
if(dfn[u] == low[u]){
++Bcnt;
do{
v = Stack[--top];
belong[v] = Bcnt;
instack[v] = false;
}while(v != u);
}
}
int main(){
scanf("%d%d", &n, &m);
int a, b;
REP(i, 0, m){
scanf("%d%d", &a, &b);
if(a > b) swap(a, b);
if(mp[mk(a, b)]) continue;
mp[mk(a, b)] = true;
vt[a].pb(b), vt[b].pb(a);
}
Index = Bcnt = top = 0;
Tarjan(1, -1);
for(int i = 1; i <= n; ++i){
int u = pre[i];
if(belong[i] != belong[u]){
deg[belong[i]]++, deg[belong[u]]++;
}
}
int cnt = 0;
for(int i = 1; i <= Bcnt; ++i){
if(deg[i] == 1) cnt++;
}
printf("%d\n", (cnt + 1) / 2);
return 0;
}
这题我之前的做法是直接去重边可以过、
但仔细想想确实会有如上的情况出现、