N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。
tarjan算法模板题,把整个强连通分量缩成一个点,入度为0的连通分量即可问题1的答案 max(入度为0的个数,出度为0的个数)即为问题2的答案
#include <cstdio> #include <cstring> #include <map> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 111; const int M = 4e4 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; struct edge{ int to,nex; }e[N * N]; int head[N],cnt; int dfn[N],low[N]; stack<int> st; int num,ck,clock[N]; bool vis[N]; int cnt1[N],cnt2[N];/// 入度,出度 void add(int u,int v) { e[cnt].to = v; e[cnt].nex = head[u]; head[u] = cnt++; } void dfs(int u) { dfn[u] = low[u] = ++num; vis[u] = true; st.push(u); for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (!dfn[v]){ dfs(v); low[u] = min(low[u],low[v]); } else if (vis[v]){ low[u] = min(low[u],low[v]); } } if (low[u] == dfn[u]){ ck++; while(true){ int x = st.top(); st.pop(); clock[x] = ck; /// 缩点 vis[x] = false; if (x == u) break; } } } void init() { memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(vis,false,sizeof(vis)); memset(low,0,sizeof(low)); memset(cnt1,0,sizeof(cnt1)); memset(cnt2,0,sizeof(cnt2)); while(!st.empty()) st.pop(); num = cnt = ck = 0; } int main() { int n; while(scanf("%d",&n) == 1){ init(); for (int i = 1; i <= n; i++){ int x; while(scanf("%d",&x) && x){ add(i,x); } } for (int i = 1; i <= n; i++){ if (dfn[i] == 0){ dfs(i); } } int ans = 0,res = 0; for (int u = 1; u <= n; u++){ for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; int x = clock[u],y = clock[v]; if (x != y){ cnt2[x]++; cnt1[y]++; } } } for (int i = 1; i <= ck; i++){ if (!cnt1[i]) ans++; if (!cnt2[i]) res++; } printf("%d\n",ans); printf("%d\n",ck == 1 ? 0 : max(ans,res)); } return 0; }给出一张无向图,求割点的个数
tarjan算法求割点模板题 要 注 意 的 是 , 不 能 像 求 连 通 分 量 把 第 二 个 m i n 写 成 l o w [ u ] = m i n ( l o w [ u ] , l o w [ v ] ) 要注意的是,不能像求连通分量把第二个min写成low[u] = min(low[u],low[v]) 要注意的是,不能像求连通分量把第二个min写成low[u]=min(low[u],low[v]) 要 写 成 l o w [ u ] = m i n ( l o w [ u ] , d f n [ v ] ) , 因 为 是 无 向 图 , 所 以 有 可 能 这 个 v 通 过 一 些 边 绕 到 u 的 父 亲 结 点 要写成low[u] = min(low[u],dfn[v]),因为是无向图,所以有可能这个v通过一些边绕到u的父亲结点 要写成low[u]=min(low[u],dfn[v]),因为是无向图,所以有可能这个v通过一些边绕到u的父亲结点 从 而 使 割 点 数 目 减 少 从而使割点数目减少 从而使割点数目减少
#include <cstdio> #include <cstring> #include <map> #include <set> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 2e4 + 5; const int M = 1e5 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; /** 求割点 一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。 (2) u不为树根,且满足存在(u,v)为树枝边(或称 父子边,即u为v在搜索树中的父亲),使得 dfn(u)<=low(v)。 (也就是说 V 没办法绕过 u 点到达比 u dfn要小的点) 注:这里所说的树是指,DFS下的搜索树*/ struct edge{ int to,nex; }e[M * 2]; int num,head[N],cnt; int dfn[N],low[N]; set<int> st; void add(int u,int v) { e[cnt].to = v; e[cnt].nex = head[u]; head[u] = cnt++; } void dfs(int u,int fa) { dfn[u] = low[u] = ++num; int son = 0; for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (!dfn[v]){ dfs(v,u); son++; if (fa == -1 && son > 1) st.insert(u); if (fa != -1 && low[v] >= dfn[u]) st.insert(u); low[u] = min(low[u],low[v]); } else if (v != fa){ low[u] = min(low[u],dfn[v]); /// dfn[v] 不能 写成 low[v] } } } void solve(int n) { for (int i = 1; i <= n; i++){ if (!dfn[i]) dfs(i,-1); } cout << st.size() << endl; } void init(int n) { st.clear(); memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); num = cnt = 0; } int main() { int n; while(scanf("%d",&n) == 1 && n){ init(n); int u,v; while(scanf("%d",&u) && u){ char ch; while(scanf("%d%c",&v,&ch)){ add(u,v); add(v,u); if (ch == '\n') break; } } solve(n); } return 0; }n个点,m条边(有重边) 问添加一条新边后,最少的剩下的桥是多少
tarjan缩点,形成一棵树,然后重新建图,把距离最远的两个结点求出来即是可减少的桥数量(可以用两次dfs求出)
#include <cstdio> #include <cstring> #include <map> #include <set> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 2e5 + 5; const int M = 1e6 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; struct node{ int to,nex,id; }e[M * 2]; struct node2{ int to,nex; }edge[M * 2]; int num; int low[N],dfn[N]; stack<int> st; bool vis[N]; int block[N],ck; int head[N],cnt; int head2[N],cnt2; vector<vector<int> > g; int mx,mx_id; void addEdge(int u,int v) { edge[cnt2].to = v; edge[cnt2].nex = head2[u]; head2[u] = cnt2++; } void add(int u,int v,int id) { e[cnt].id = id; e[cnt].to = v; e[cnt].nex = head[u]; head[u] = cnt++; } void tarjan(int u,int id) { low[u] = dfn[u] = ++num; vis[u] = true; st.push(u); for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (id == e[i].id) continue; if (!dfn[v]){ tarjan(v,e[i].id); low[u] = min(low[u],low[v]); } else if (vis[v]){ low[u] = min(low[u],dfn[v]); } } if (dfn[u] == low[u]){ ck++; while(true){ int x = st.top(); st.pop(); block[x] = ck; vis[x] = false; if (x == u) break; } } } void dfs(int u,int fa,int d) { if (d > mx){ mx_id = u; mx = d; } for (int i = head2[u]; i != -1; i = edge[i].nex){ int v = edge[i].to; if (v == fa) continue; dfs(v,u,d + 1); } } void solve(int n) { for (int i = 1; i <= n; i++){ if (!dfn[i]) tarjan(i,-1); } for (int u = 1; u <= n; u++){ for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; int x = block[u],y = block[v]; if (x == y) continue; addEdge(x,y); } } dfs(1,-1,0); dfs(mx_id,-1,0); printf("%d\n",ck - 1 - mx); } void init() { while(!st.empty()) st.pop(); memset(vis,false,sizeof(vis)); memset(head,-1,sizeof(head)); memset(head2,-1,sizeof(head2)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(block,0,sizeof(block)); cnt = cnt2 = ck = num = 0; mx = -1; } int main() { int n,m; while(scanf("%d %d",&n,&m) == 2 && (n || m)){ init(); for (int i = 0; i < m; i++){ int u,v; scanf("%d %d",&u,&v); add(u,v,i); add(v,u,i); } solve(n); } return 0; }给出了一个有N个节点和M条边的简单有向图。请告诉我你可以添加的最大边数,该图仍然是一个简单的有向图。另外,在添加这些边之后,这个图不能是强连接的。 简单有向图是没有多条边或图循环的有向图。强连通有向图是一种有向图,在有向图中,通过沿边指向的方向遍历边,可以从任何其他节点开始到达任何节点。
最终情况一定是再加一条边,整张图就是强连通的了,那么我们可以把图看成2部分x和y,x和y都是完全图,然后x每个点到y每个点都有边,但是y到x没有边,如果有肯定是强连通了,设x中有a个点,y中有b个点 则 a + b = n 则边数就是 a * (a - 1) + b * (b - 1) + a * b - m,化简得到:n * n - a * b - n - m; 如何让这个值大那就是如何选取x和y的问题了,显然a和b差距越大越好,那么就可以通过tarajan来找出一个包含点最少的强连通分量来当x,其他的强连通分量作为y,这样就很容易了
#include <cstdio> #include <cstring> #include <map> #include <set> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 5; const int M = 1e5 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; struct node{ int to,nex; }e[M * 2]; int num[N],ord; int low[N],dfn[N]; stack<int> st; bool vis[N]; int block[N],ck; int head[N],cnt; int in[N],out[N]; /// 入度,出度 void add(int u,int v) { e[cnt].to = v; e[cnt].nex = head[u]; head[u] = cnt++; } void tarjan(int u) { low[u] = dfn[u] = ++ord; vis[u] = true; st.push(u); for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (!dfn[v]){ tarjan(v); low[u] = min(low[u],low[v]); } else if (vis[v]){ low[u] = min(low[u],dfn[v]); } } if (dfn[u] == low[u]){ ck++; while(true){ int x = st.top(); st.pop(); block[x] = ck; num[ck]++; vis[x] = false; if (x == u) break; } } } void solve(int n,int m) { for (int i = 1; i <= n; i++){ if (!dfn[i]) tarjan(i); } if (ck == 1){ printf("-1\n"); return ; } for (int u = 1; u <= n; u++){ for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (block[u] == block[v]) continue; in[block[u]]++; out[block[v]]++; } } ll ans = 0; ll sum = n * 1ll * n - n - m; for (int i = 1; i <= ck; i++){ if (in[i] == 0 || out[i] == 0){ ans = max(ans,(sum - (n - num[i]) * 1ll * num[i])); } } printf("%I64d\n",ans); } void init() { while(!st.empty()) st.pop(); memset(vis,false,sizeof(vis)); memset(head,-1,sizeof(head)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(block,0,sizeof(block)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(num,0,sizeof(num)); cnt = ck = ord = 0; } int main() { int t,Case = 1; scanf("%d",&t); while(t--){ init(); int n,m; scanf("%d %d",&n,&m); for (int i = 0; i < m; i++){ int u,v; scanf("%d %d",&u,&v); add(u,v); } printf("Case %d: ",Case++); solve(n,m); } return 0; }n个王子和m个公主,王子只能和他喜欢的公主结婚,公主可以和所有的王子结婚,输出所有王子可能的结婚对象, 必须保证王子与任意这些对象中的一个结婚,都不会影响到剩余的王子的配对数,也就是不能让剩余的王子中突然有一个人没婚可结了。
首先建图,如果王子u喜欢妹子v,则建一条边u指向v(u,v),对于大臣给出的初始完美匹配,如果王子u和妹子v结婚,则建一条边v指向u(v,u),然后求强连通分量, 对于每个王子和妹子,如果他们都在同一个强连通分量内,则他们可以结婚。 为什么呢?因为每个王子只能和喜欢的妹子结婚,初始完美匹配中的丈夫和妻子之间有两条方向不同的边可以互达,则同一个强连通分量中的王子数和妹子数一定是相等的,若王子x可以和另外的一个妹子a结婚,妹子a的原配王子y肯定能找到另外一个妹子b结婚,因为如果找不到的话,则x和a必不在同一个强连通分量中。 所以一个王子可以和所有与他同一强连通分量的妹子结婚,而这不会导致同一强连通分量中的其他王子找不到妹子结婚。
首先做一次最大匹配,设为cnt,那么对于左边,有n-cnt个王子没有匹配,对于右边,有m-cnt个公主没有匹配,所以我们在左边加上m-cnt个虚拟点,这些点喜欢所有公主,右边加上n-cnt个虚拟点,这些点被所有王子喜欢,这样左右两边都是n+m-cnt个点,在求一次最大匹配,这一定是一个完备匹配,对于每一个王子,用他目前匹配的公主,向所有他喜欢的公主连一条有向边,这表示单方面可以替换,所以再对得到的新图求强连通,处在一个强连通分量的公主可以相互替换
#include <cstdio> #include <cstring> #include <map> #include <set> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 2222; const int M = 2e6 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; struct node{ int to,nex; }e[M]; int head[N],cnt; int ord; int low[N],dfn[N]; stack<int> st; bool vis[N]; bool g[N][N]; int link[N],link2[N]; int n,m,num_n,num_m; int block[N],ck; vector<int> ans; void add(int u,int v) { e[cnt].nex = head[u]; e[cnt].to = v; head[u] = cnt++; } void tarjan(int u) { low[u] = dfn[u] = ++ord; vis[u] = true; st.push(u); for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (!dfn[v]){ tarjan(v); low[u] = min(low[u],low[v]); } else if (vis[v]){ low[u] = min(low[u],dfn[v]); } } if (dfn[u] == low[u]){ ck++; while(true){ int x = st.top(); st.pop(); block[x] = ck; vis[x] = false; if (x == u) break; } } } bool dfs(int x) { for (int i = 1; i <= num_m; i++){ if (!g[x][i]) continue; if (vis[i]) continue; vis[i] = true; if (!link[i] || dfs(link[i])){ link[i] = x; return true; } } return false; } int match() { int ans = 0; memset(link,0,sizeof(link)); for (int i = 1; i <= num_n; i++){ memset(vis,false,sizeof(vis)); if (dfs(i)) ans++; } return ans; } void solve() { num_n = n; num_m = m; int res = match(); num_n = n + m - res; num_m = num_n; for (int i = 1; i <= num_n; i++){ for (int j = m + 1; j <= num_m; j++){ g[i][j] = true; } } for (int i = n + 1; i <= num_n; i++){ for (int j = 1; j <= num_m; j++){ g[i][j] = true; } } res = match(); for (int i = 1; i <= num_m; i++){ link2[link[i]] = i; } for (int i = 1; i <= num_n; i++){ for (int j = 1; j <= num_m; j++){ if (g[i][j] && link2[i] != j){ add(link2[i],j); } } } memset(vis,false,sizeof(vis)); for (int i = 1; i <= num_m; i++){ if (!dfn[i]) tarjan(i); } for (int i = 1; i <= n; i++){ ans.clear(); for (int j = 1; j <= m; j++){ if (g[i][j] && block[j] == block[link2[i]]){ ans.push_back(j); } } printf("%d",ans.size()); for (auto k : ans){ printf(" %d",k); } printf("\n"); } } void init() { while(!st.empty()) st.pop(); memset(head,-1,sizeof(head)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(block,0,sizeof(block)); memset(link2,0,sizeof(link2)); memset(g,false,sizeof(g)); cnt = ck = ord = 0; } int main() { int t,Case = 1; scanf("%d",&t); while(t--){ init(); scanf("%d %d",&n,&m); for (int i = 1; i <= n; i++){ int k; scanf("%d",&k); for (int j = 0; j < k; j++){ int x; scanf("%d",&x); g[i][x] = true; } } printf("Case #%d:\n",Case++); solve(); } return 0; }有n个点,m条边(重边),每条边都有一个权值,问摧毁一个桥所需要的最少权值是多少
重边的第二种处理方法,直接通过堆栈和low[v] > dfn[u]进行判断 如果图不连通,那么不需要派人去 如果有一条桥,权值为0,那么答案应该是1
#include <cstdio> #include <cstring> #include <map> #include <set> #include <vector> #include <stack> #include <iostream> #include <algorithm> using namespace std; const int N = 2e5 + 5; const int M = 1e6 + 5; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; typedef long long ll; struct node{ int to,nex,id,len; }e[M * 2]; int ord; int low[N],dfn[N]; stack<int> st; bool vis[N]; int head[N],cnt; vector<int> ans; void add(int u,int v,int id,int len) { e[cnt].id = id; e[cnt].to = v; e[cnt].len = len; e[cnt].nex = head[u]; head[u] = cnt++; } void tarjan(int u,int id) { low[u] = dfn[u] = ++ord; vis[u] = true; st.push(u); for (int i = head[u]; i != -1; i = e[i].nex){ int v = e[i].to; if (id == e[i].id) continue; if (!dfn[v]){ tarjan(v,e[i].id); if (low[v] > dfn[u]){ /// 第二个min没有问题,那这里就能这样写 ans.push_back(e[i].len); } low[u] = min(low[u],low[v]); } else if (vis[v]){ /// 因为有重边,所以必须要在堆栈中才能更新 low[u] = min(low[u],dfn[v]); } } if (dfn[u] == low[u]){ while(true){ int x = st.top(); st.pop(); vis[x] = false; if (x == u) break; } } } void solve(int n) { int res = 0; for (int i = 1; i <= n; i++){ if (!dfn[i]) { tarjan(i,-1); res++; } } if (res > 1){ printf("0\n"); return ; } if (ans.size() == 0){ printf("-1\n"); return ; } sort(ans.begin(),ans.end()); printf("%d\n",ans[0] == 0 ? 1 : ans[0]); } void init() { ans.clear(); while(!st.empty()) st.pop(); memset(vis,false,sizeof(vis)); memset(head,-1,sizeof(head)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); cnt = ord = 0; } int main() { int n,m; while(scanf("%d %d",&n,&m) == 2 && (n || m)){ init(); for (int i = 0; i < m; i++){ int u,v,w; scanf("%d %d %d",&u,&v,&w); add(u,v,i,w); add(v,u,i,w); } solve(n); } return 0; }