Tarjan总结(缩点+割点(边)+双联通+LCA+相关模板)
Tarjan求强连通分量
先来一波定义
强连通:有向图中A点可以到达B点,B点可以到达A点,则称为强连通
强连通分量:有向图的一个子图中,任意两个点可以相互到达,则称当前子图为图的强连通分量
强连通图: 如果在一个有向图中,每两个点都强连通,我们就叫这个图叫强连通图。
(一张十分简洁的图)
如图,图中{1,2}就是一个强连通,也是这个图中的一个强连通分量
求强连通分量的算法有三种:
Kosaraju算法,Tarjan算法,Gabow算法(然而我只会用Tarjan求)
这里就稍微介绍一下tarjan求强连通分量
首先要明白几个概念:
时间戳(dfn):dfn就是在搜索中某一节点被遍历到的次序号,
节点能追溯到的最早的栈中节点的时间戳(low):low就是某一节点在栈中能追溯到的最早的父亲节点的时间戳。
Tarjan算法就是基于深度优先搜索的算法:把没有标记过时间戳的点放入栈中,然后以它为根进行搜索,搜索完后回溯更新low,保证low一定是最小的,如果发现low[u] == dfn[u],就把u上面的全部弹出栈内,这些点就构成了强连通分量,而这个u就是一个关键节点:从这个节点能够到达其强连通分量中的其他节点,但是没有其他属于这个强连通分量以外的点能够到达这个点,所以这个点的low[u]值维护完了之后还是和dfn[u]的值一样
这样描述还是太抽象了点,还是走一遍流程吧
cnt为计数器
一张经典的图:
搜到1,1入栈,dfn[1] = low[1] = ++cnt,栈内元素:1
搜到2,2没被搜过,2入栈,dfn[2] = low[2] = ++cnt,栈内元素:1,2
搜到3,3没被搜过,3入栈,dfn[3] = low[3] = ++cnt,站内元素:1,2,3
搜到6,6没被搜过,6入栈,dfn[6] = low[6] = ++cnt,站内元素:1,2,3,6
6没有出边,此时
发现low[6] == dfn[6],把6弹出,6为强连通分量
回溯到3,发现low[3] == dfn[3],把3弹出,3为强连通分量
回溯到2,发现2还可以到5,搜索5
搜到5,5没被搜过,5入栈,dfn[5] = low[5] = ++cnt,栈内元素1,2,5
搜到1,发现1被搜过了且在栈内,因为low是节点能追溯到的最早的栈中节点的时间戳,我们要保证low最小,所以我们要更新low[5] = min(low[5],dfn[1]) = 1
再搜到6,6被搜过但不再栈内,不管
没有其他边了,回溯,回溯到2,因为low要最小,而后面的low值要<=当前的,所以用后面的更新当前的,更新2的值low[2] = min(low[2],low[5])
回溯到1
继续搜,搜到4,4没有被搜过,4入栈,栈内元素:1,2,5,4
再搜到5,发现5在栈内,就更新low[4],low[4] = min(low[4],dfn[5]) = 5
没有其他的边了,然后回溯,回溯到1
发现dfn[1] == low[1]
于是乎我们将所有1上面的元素及1弹栈,得到一个强连通分量1,2,5,4
于是这个图中就有3个强连通分量
代码:
- //vis[i]表示i是否在栈中
- //sum表示是连通块个数
- //size[i]表示第i个连通块内的元素个数
- //bel[i]表示i属于哪个连通块belong也可以理解为color
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++ cnt;
- vis[u] = ;//标记是否在栈内
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[v]) low[u] = min(low[u],dfn[v]);
- }
- if(low[u] == dfn[u]) {
- ;sum ++;
- while(x != u) {//x枚举栈内元素
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- size[sum] ++;
- }
- }
- }
但因为并不一定所有的点都相连,如
所以我们遍历的时候要:
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
例题:
很多题都会用到缩点的思想,所以这里先给出两个练手的
- //求强连通分量的个数是不是等于1
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n,m,num,cnt,sum;
- int dfn[N],low[N],head[N],bel[N];
- bool vis[N];
- struct node {
- int v,nx;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch =getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[u]) low[u] = min(dfn[v],low[u]);
- }
- if(dfn[u] == low[u]) {
- ;sum ++;
- while(u != x) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- }
- }
- }
- int main() {
- while(scanf("%d%d",&n,&m)) {
- && m == ) break;
- memset(head,-,sizeof(head));
- memset(vis,,sizeof(vis));
- memset(dfn,,sizeof(dfn));
- memset(low,,sizeof(low));
- memset(bel,,sizeof(bel));
- memset(e,,sizeof(e));
- num = sum = cnt = ;
- ,x,y; i <= m; ++ i) read(x),read(y),add(x,y);
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ) printf("Yes\n");
- else printf("No\n");
- }
- ;
- }
HDU 1269
P2863 [USACO06JAN]牛的舞会The Cow Prom
- //求联通块内的元素个数是否大于1
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <stack>
- using namespace std;
- const int N = 1e6;
- int n,m,num,cnt,tot,sum,ans;
- int a[N],b[N],head[N],size[N],bel[N],dfn[N],low[N];
- bool vis[N];
- stack<int>s;
- struct node {
- int v,nx;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[u]) low[u] = min(dfn[v],low[u]);
- }
- if(dfn[u] == low[u]) {
- int x;sum ++;
- while(u != x) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- size[sum] ++;
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n),read(m);
- ,x,y; i <= m; i ++) {
- read(x),read(y);
- add(x,y);
- }
- ; i <= n; i ++) if(!dfn[i]) tarjan(i);
- ; i <= sum; i ++) {
- )
- ans ++;
- }
- printf("%d",ans);
- ;
- }
P2863
Tarjan缩点
个人觉得分不分开讲一个样,缩点就是在上面的内容之后附加一个操作
其实缩点可以看做是一种解题方法罢
简单说,就是把一个强连通分量里的点做为一个点处理
也就是把bel数组里的元素当成点看就好了
假设我们有一张图长这个样:
那我们操作的时候就可以脑补成这样
---->
emmmm.....一般题目都会说如果相互到达就balabalbala
具体代码呢,就是枚举某个点的连边连向的点,如果在同一分量里,就不予处理,否则就按题目要求处理
- ; i <= n; ++ i)
- for(int j = head[i]; ~j; j = e[j].nx) {
- int v = e[j].v;
- if(bel[i] != bel[v]) /*.code.*/
- }
异常的容易
那就直接看题目吧:
- //求入度为0的点的个数
- #include <bits/stdc++.h>
- using namespace std;
- ;
- ;
- int n,m,cnt,sum,ans,num;
- int head[N],dfn[N],low[N],bel[N],in[N];
- bool vis[N];
- struct node {
- int v,nx;
- }e[M];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[v]) low[u] = min(low[u],dfn[v]);
- }
- if(low[u] == dfn[u]) {
- int x;sum ++;
- while(x != u) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n),read(m);
- ,x,y; i <= m; ++ i) {
- read(x),read(y);
- if(x == y) continue;
- add(x,y);
- }
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= n; ++ i)
- for(int j = head[i]; ~j; j = e[j].nx) {
- int v = e[j].v;
- if(bel[i] != bel[v]) in[bel[v]] ++;
- }
- // for(int i = 1; i <= n; ++ i) cout<<in[bel[i]]<<" ";
- ; i <= sum; ++ i) if(!in[i]) ans ++;
- cout<<ans;
- ;
- }
P2002
- //找出度为0的点
- //代码历史久远,以2002为准
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <stack>
- using namespace std;
- const int N = 1e6;
- int n,m,tot,sum,ans,cnt,num;
- int a[N],b[N],head[N],dfn[N],low[N],size[N],bel[N];
- bool vis[N],mark[N];
- struct node {
- int v,nx;
- }e[N];
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- 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]) {
- int x;sum ++;
- while(u != x) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- size[sum] ++;
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- scanf("%d%d",&n,&m);
- ; i <= m; i ++) {
- scanf("%d%d",&a[i],&b[i]);
- add(a[i],b[i]);
- }
- ; i <= n; i ++) if(!dfn[i]) tarjan(i);
- ; i <= m; i ++) ;
- ; i <= sum; i ++) if(!mark[i]) ans = size[i],tot++;
- ) printf("%d",ans);
- ");
- ;
- }
P2341
- /*
- tarjan
- 第一问求所有联通块的块中最小值之和,开一个mn数组在tarjan中记录
- 根据乘法原理,第二问求所有块中 价值=最小值的个数 的乘积
- */
- #include <bits/stdc++.h>
- #define ll long long
- using namespace std;
- const int N = 1e6;
- ;
- ll n,m,num,cnt,sum,ans,ans1 = ;
- ll mn[N],dfn[N],low[N],bel[N],size[N],a[N],head[N];
- bool vis[N];
- struct node {
- ll v,nx;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;ll f = ;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return;
- }
- inline void add(ll u,ll v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<ll>s;
- void tarjan(ll u) {
- dfn[u] = low[u] = ++ cnt;
- vis[u] = ;
- s.push(u);
- for(ll i = head[u]; ~i; i = e[i].nx) {
- ll v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[v]) low[u] = min(low[u],dfn[v]);
- }
- if(low[u] == dfn[u]) {
- ll x=-;sum ++;
- while(x != u) {
- x = s.top(),s.pop();
- bel[x] = sum,vis[x] = ;
- if(mn[sum] == a[x]) size[sum] ++;
- else if(mn[sum] > a[x]) {
- mn[sum] = a[x];
- size[sum] = ;
- }
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n);
- ; i <= n; ++ i) mn[i] = 0x7fffffff;
- ; i <= n; ++ i) read(a[i]);
- read(m);
- ,x,y; i <= m; ++ i) read(x),read(y),add(x,y);
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= sum; ++ i) {
- ans += mn[i];
- ans1 = ans1 * size[i] % mod;
- }
- cout<<ans<<" "<<ans1;
- ;
- }
P2194
P2746 [USACO5.3]校园网Network of Schools
- //同样历史久远
- //1.求缩点后入度为0 的点的个数
- //2.求入度为0的点数与出度为0的点的较大值
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n,m,num,sum,cnt,j,ans,ans1;
- int dfn[N],low[N],size[N],bel[N],x,kk[N][N],head[N];
- bool vis[N],mark[N],mark1[N];
- struct node {
- int v,nx;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- s.push(u);
- vis[u] = ;
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- 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]) {
- int x;sum ++;
- while(x != u) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- size[sum] ++;
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n);
- ; i <= n; ++ i) {
- ; j <= n + ; ++ j) {
- read(x);
- ) break;
- kk[i][j] = x;
- add(i,x);
- }
- }
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= n; ++ i) {
- ; kk[i][j] != ; j ++) ,mark1[bel[i]] = ;
- }
- ) {
- printf("%d\n0",sum);
- ;
- }
- ; i <= sum; ++ i) {
- if(!mark[i]) ans ++;
- if(!mark1[i]) ans1 ++;
- }
- printf("%d\n%d",ans,max(ans,ans1));
- }
P2194
P2812 校园网络【[USACO]Network of Schools加强版】
- //邻接表
- #include <bits/stdc++.h>
- using namespace std;
- ;
- ;
- int n,num,sum,cnt,ans1,ans2;
- int low[N],dfn[N],head[N],size[N],bel[N];
- bool vis[N],mark[N],mark1[N];
- struct node {
- int v,nx;
- }e[M];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++ cnt;;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- 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]) {
- ;sum ++;
- while(x != u) {
- x = s.top(),s.pop();
- bel[x] = sum,vis[x] = ;
- size[sum] ++;
- }
- }
- }
- int main(int argc, char const *argv[]) {
- memset(head,-,sizeof(head));
- read(n);
- ,x; i <= n; ++ i) {
- ; j <= n + ; ++ j) {
- read(x);
- ) break;
- add(i,x);
- }
- }
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= n; ++ i) {
- for(int j = head[i]; ~j; j = e[j].nx) {
- if(bel[i] != bel[e[j].v]) {
- mark[bel[e[i].v]] = ;
- mark1[bel[i]] = ;
- }
- }
- }
- ; i <= sum; ++ i) {
- if(!mark[i]) ans1 ++;
- if(!mark1[i]) ans2 ++;
- }
- printf("%d\n%d\n", ans1,ans2);
- ;
- }
P2812
- /*
- 主体还是一个tarjan
- 因为最后还会组成n对情侣的话
- 找恋人的人和恋人和男方会在一个联通块内
- 因为男女必须成对出现,所以联通块内的点得个数为偶数,所以联通块内每个人都会配对
- 经过路径是boy1->girl2->boy2->gril1->boy1
- */
- #include <bits/stdc++.h>
- using namespace std;
- ;
- ;
- int n,m,k,num,sum,cnt,tmp;
- int head[N],dfn[N],bel[N],low[N],sta[N];
- bool vis[N];
- string g,b;
- map<string,int>mp;
- struct node {
- int v,nx;
- }e[M];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- 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]) {
- int x;sum ++;
- while(u != x) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n);
- ; i <= n; ++ i) {
- cin>>g>>b;
- mp[g] = i,mp[b] = i + n;
- add(i,i + n);
- }
- read(m);
- ; i <= m; ++ i) {
- cin>>g>>b;
- add(mp[g],mp[b]),add(mp[b],mp[g]);
- }
- ; i <= n * ; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= n; ++ i) {
- if(bel[i] == bel[i + n]) printf("Unsafe\n");
- else printf("Safe\n");
- }
- ;
- }
P1407
以下两个可能用到其他小知识
- //tarjan+最短路
- #include <bits/stdc++.h>
- using namespace std;
- ;
- ;
- const int INF = 0x3f3f3f3f;
- int n,m,sum,num,num_new,cnt;
- int dfn[N],low[N],head[N],head_new[N],bel[N],dis[N];
- bool vis[N],vis_new[N];
- struct edge {
- int v,nx,w;
- }e[N];
- struct edge_new {
- int v,nx,w;
- }e_new[N];
- struct node {
- int id,dis;
- bool operator < (const node &l) const {
- return dis > l.dis;
- }
- };
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return;
- }
- inline void add(int u,int v,int w) {
- e[++num].nx = head[u],e[num].v = v,e[num].w = w,head[u] = num;
- }
- inline void add_new(int u,int v,int w) {
- e_new[++num_new].nx = head_new[u],e_new[num_new].v = v,e_new[num_new].w = w,head_new[u] = num_new;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++ cnt;
- vis[u] = ;
- s.push(u);
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
- else if(vis[v]) low[u] = min(low[u],dfn[v]);
- }
- if(low[u] == dfn[u]) {
- int x;sum ++;
- while(u != x) {
- x = s.top(),s.pop();
- vis[x] = ,bel[x] = sum;
- }
- }
- }
- void dijkstra(int s) {
- priority_queue<node>q;
- memset(dis,INF,sizeof(dis));
- memset(vis_new,,sizeof(vis_new));
- dis[s] = ;
- q.push((node){s,});
- while(!q.empty()) {
- node d = q.top();q.pop();
- int u = d.id;
- if(vis_new[u]) continue;
- vis_new[u] = ;
- for(int i = head_new[u]; ~i; i = e_new[i].nx) {
- int v = e_new[i].v;
- if(dis[v] > dis[u] + e_new[i].w) {
- dis[v] = dis[u] + e_new[i].w;
- q.push((node){v,dis[v]});
- }
- }
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- memset(head_new,-,sizeof(head_new));
- read(n),read(m);
- ,x,y,z; i <= m; ++ i) read(x),read(y),read(z),add(x,y,z);
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
- ; i <= n; ++ i)
- for(int j = head[i]; ~j; j = e[j].nx) {
- int v = e[j].v;
- if(bel[i] != bel[v]) add_new(bel[i],bel[v],e[j].w);
- }
- dijkstra(bel[]);
- printf("%d\n",dis[bel[n]]);
- ;
- }
- /*
- tarjan缩点求最小路径覆盖集
- */
- #include <bits/stdc++.h>
- using namespace std;
- ;
- ;
- int t, n, m, num, num_new, sum, cnt, ans;
- int head[N], low[N], dfn[N], link[N], head_new[N], bel[N];
- bool vis[N], vis_new[N];
- struct node {
- int v, nx;
- } e[M], e_new[M];
- template<class T>inline void read(T &x) {
- x = ; ; char ch = getchar();
- while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
- + ch - ', ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u, int v) {
- e[++num].nx = head[u], e[num].v = v, head[u] = num;
- }
- inline void add_new(int u, int v) {
- e_new[++num_new].nx = head_new[u], e_new[num_new].v = v, head_new[u] = num_new;
- }
- stack<int>s;
- void tarjan(int u) {
- dfn[u] = low[u] = ++cnt;
- vis[u] = ;
- s.push(u);
- for (int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
- else if (vis[v]) low[u] = min(low[u], dfn[v]);
- }
- if (low[u] == dfn[u]) {
- int x; sum++;
- while (x != u) {
- x = s.top(), s.pop();
- bel[x] = sum, vis[x] = ;
- }
- }
- }
- bool dfs(int u) {
- for (int i = head_new[u]; ~i; i = e_new[i].nx) {
- int v = e_new[i].v;
- if (!vis_new[v]) {
- vis_new[v] = ;
- || dfs(link[v])) {
- link[v] = u;
- ;
- }
- }
- }
- ;
- }
- int main(int argc, char const *argv[]) {
- read(t);
- while (t --) {
- read(n), read(m);
- memset(vis, , sizeof(vis));
- memset(low, , sizeof(low));
- memset(dfn, , sizeof(dfn));
- memset(bel, , sizeof(bel));
- memset(head, -, sizeof(head));
- memset(link, -, sizeof(link));
- memset(head_new, -, sizeof(head_new));
- memset(e, , sizeof(e));
- memset(e_new, , sizeof(e_new));
- ans = num = cnt = num_new = sum = ;
- , x, y; i <= m; ++ i) read(x), read(y), add(x, y);
- ; i <= n; ++i) if (!dfn[i]) tarjan(i);
- ; i <= n; ++i)
- for (int j = head[i]; ~j; j = e[j].nx) {
- int v = e[j].v;
- if (bel[i] != bel[v]) add_new(bel[i], bel[v]);
- }
- ; i <= sum; ++i) {
- memset(vis_new, , sizeof(vis_new));
- if (dfs(i)) ans ++;
- }
- printf("%d\n", sum - ans);
- }
- ;
- }
HDU 3861
Tarjan割点&&割边
割点
割点:在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点(Articulation Point)。
一个没有关节点的连通图称为重连通图(biconnected graph)。若在连通图上至少删去k 个顶点才能破坏图的连通性,则称此图的连通度为k。
some小例子
割点为1的图(删去1和他的边图变成两部分) 连通度为2的图
首先明白几个概念
(a) (b)
概念:
DFS树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树,如图(b)所示。
树边:如(b)中的实线所示,可理解为在DFS过程中访问未访问节点时所经过的边
回边:如(b)中的虚线所示,可理解为在DFS过程中遇到已访问节点时所经过的边。
观察我们的DFS树(b),发现它有两种情况可能是割点:
1、对于一个根节点u,若有两个及以上的子树,说明u是割点
2、对于非叶子节点u(非根节点),若其子树均没有连向u祖先节点的回边,说明删掉u后,根节点与子树不再相连,则u是割点
对根节点很好处理,只要记录一下它的子树个数就好了,对于非叶节点呢
对!用low数组
我们用low记录节点u或u的子树通过非父子边追溯到最早的祖先节点
low[u] = min(low[u],low[v])(当(u,v)是树边)
low[u] = min(low[u],dfn[v])(当(u,v)是回边且v不等于u的父节点)
对于情况2,(u,v)是树边且当 low[v] ≥ dfn[u] 时,节点u是割点,因为v能回溯到的最早祖先不是u就是v,所以v并不能连到u的祖先上,当删去u时,v和u的祖先不相连,即u是割点
fa的意思就是从哪个点搜过来的,因为是无向图,可能还会搜到之前的点
代码实现:
- //cut标记当前点是否是割点
- void tarjan(int u,int fa) {
- dfn[u] = low[u] = ++cnt;
- ;//记录子树个数
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) {
- tarjan(v,u);
- low[u] = min(low[u],low[v]);
- ;//情况2
- if(u == fa) child ++;
- }
- low[u] = min(low[u],dfn[v]);
- }
- && u == fa) cut[u] = ;//情况1
- }
例题:
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n,m,num,cnt,tot;
- int dfn[N],low[N],head[N];
- bool cut[N];
- struct node {
- int v,nx;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- void tarjan(int u,int fa) {
- dfn[u] = low[u] = ++cnt;
- ;
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) {
- tarjan(v,u);
- low[u] = min(low[u],low[v]);
- ;
- if(u == fa) child ++;
- }
- low[u] = min(low[u],dfn[v]);
- }
- && u == fa) cut[u] = ;
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n),read(m);
- ,x,y; i <= m; ++ i) read(x),read(y),add(x,y),add(y,x);
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
- ; i <= n; ++ i) if(cut[i]) tot ++;
- printf("%d\n",tot);
- ; i <= n; ++ i) if(cut[i]) printf("%d ",i);
- ;
- }
P3388
- //输出最小的割点编号
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n,m,k,num,cnt,x,y;
- int dfn[N],low[N],head[N];
- bool cut[N],vis[N];
- struct node {
- int nx,v;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- void tarjan(int u,int fa) {
- dfn[u] = low[u] = ++cnt;
- ;
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) {
- tarjan(v,u);
- low[u] = min(low[u],low[v]);
- ;
- if(u == fa) child ++;
- }
- low[u] = min(low[u],dfn[v]);
- }
- ) cut[u] = ;
- }
- void dfs(int u) {
- vis[u] = ;
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!vis[v]) dfs(v);
- }
- }
- int main() {
- memset(head,-,sizeof(head));
- read(n);
- while(scanf("%d%d",&x,&y)) {
- && y == ) break;
- add(x,y),add(y,x);
- }
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
- read(x),read(y);
- ; i <= n; ++ i) {
- if(cut[i]) {
- memset(vis,,sizeof(vis));
- vis[i] = ;
- dfs(x);
- if(!vis[y]) {
- cout<<i;
- ;
- }
- }
- }
- cout<<"No solution";
- ;
- }
P5058
割边
割边的话其实也差不多甚至更简单
割边:也叫桥,当且仅当去掉该边之后的子图不连通
割边是用在无向图内的
割边不用记录子树情况,然后再改一句话把low[v] >= dfn[u]改为low[v] > dfn[u]
因为我们割点的时候是把当前节点u的所有的边都删去,而割边只能删一条边,显然,如果u,v之间有两条边的话,如果其中删掉一条边,另一条还和u相连,原图还保持连通
代码:
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n,m,num,cnt;
- int low[N],dfn[N],head[N];
- struct node {
- int v,nx,id;
- }e[N];
- template<class T>inline void read(T &x) {
- x = ;;char ch = getchar();
- while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
- + ch - ',ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u,int v) {
- e[++num].nx = head[u],e[num].v = v,head[u] = num;
- }
- void tarjan(int u,int fa) {
- dfn[u] = low[u] = ++cnt;
- for(int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if(!dfn[v]) {
- tarjan(v,u);
- low[u] = min(low[u],low[v]);
- if(low[v] > dfn[u]) printf("%d -- %d is a Cutting edge\n",u,v);
- }
- else if(v != fa) low[u] = min(low[u],dfn[v]);
- }
- }
- int main(int argc, char const *argv[]) {
- memset(head,-,sizeof(head));
- read(n),read(m);
- ,x,y; i <= m; ++ i) {
- read(x),read(y);
- add(x,y),add(y,x);
- }
- ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
- ;
- }
点双连通分量&&边双连通分量
这东西我是现学现卖的,可能会有些地方不详细
既然我们要求点双连通分量和边双连通分量,那我们就要知道它们是什么
连通:在无向图中,所有的点能相互到达
连通分量:相互连通的子图
点双连通分量:简单说,就是没有割点的子图,也就是删掉一个点后,图仍连通性
边双连通分量:简单说,就是没有割边的子图,也就是删掉一个边后,图仍连通性
如果先看具体定义,出门右转自己搜吧(其实是我懒得放百度链接,万一你们都用wiki呢)
这样说很抽象的话,还是来两张图好了
(1) (2)
如图(1)所示:1是割点,{1,2,4,3} 是一个点双连通分量,{1,5,7,6} 也是一个点双连通分量,不难发现,割点可能存在于多个点双连通分量里,而其他点只可能存在于一个点双连通分量里,同时,{1,2,3,4,5,6,7,8} 也是一个边双连通 分量;
如图(2)所示,1-4 是割边,在这个图内 {1,2,3} 和 {4,5,6} 分别组成了连个边双连通分量
求点双连通分量:
对于点双连通分量,在求割点的时候就可以顺便求出,但这是我们要建立一个栈,然后在搜索图的时候,让边入栈,如果遇到 low[v] ≥ dfn[u] 即u是割点的时候,将 u - v 这条边之上的边全部弹出,这些边以及相邻的点就组成了一个点双连通分量,割点可能会属于多个双连通分量,其余的点只会属于一个双连通分量;
文字还是太无力,简单走一下流程:
一张简单图:
我们从1开始搜索,dfn[1] = 1,low[1] = 1
搜到2,2没被搜过(时间戳dfn),dfn[2] = 2,low[2] = 2,边 1-2 入栈,栈内元素:{1-2}
搜到3,3没被搜过,dfn[3] = 3,low[3] = 3,边2-3入栈,栈内元素:{1-2,2-3}
搜到4,4没被搜过,dfn[4] = 4,low[4] = 4,边3-4入栈,栈内元素:{1-2,2-3,3-4}
搜4,搜到了2,2被搜过,且4不是2搜过来的,low[4] = min(low[4],dnf[2]) = 2,2-4入栈,栈内元素:{1-2,2-3,3-4,4-2}
回到3,low[3] = min(low[3],low[4]) = 2
回到2,low[3] == low[2] 所以 2 是割点,弹栈,将边 2-3 以上的边全部弹出,将他们边连的顶点存入一个数组内,记为一个双连通分量
最后回到1,dfn[1] == low[1],弹栈,所以{1,2}也是一个点双连通分量
这样,我们就找到了点双联通分量 {1,2},{2,3,4};
这里同样还是用fa记录从那个点搜到了当前点
code:
- int dfn[N], low[N], head[N], bel_bcc[N], mn[N];
- bool cut[N];
- struct edge {//从u到v的边
- int u, v;
- };
- struct node {//建图用
- int v, nx;
- } e[N];
- stack<edge>s;//栈内存的是边,
- vector<int>bcc[N];
- inline void add(int u, int v) {
- e[++num].nx = head[u], e[num].v = v, head[u] = num;
- }
- void tarjan(int u, int fa) {
- dfn[u] = low[u] = ++cnt;
- ;
- for (int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if (!dfn[v]) {
- s.push((edge) {u, v});
- child ++;
- tarjan(v, u);
- low[u] = min(low[u], low[v]);
- if (low[v] >= dfn[u]) {//是割点
- cut[u] = ;
- bcc_sum ++;//点双的个数++
- // bcc[bcc_sum].clear();
- ) {
- edge d = s.top(); s.pop();
- if (bel_bcc[d.u] != bcc_sum) bcc[bcc_sum].push_back(d.u),bel_bcc[d.u] = bcc_sum;//标记某点在这个电双块内
- if (bel_bcc[d.v] != bcc_sum) bcc[bcc_sum].push_back(d.v),bel_bcc[d.v] = bcc_sum;
- if (d.u == u && d.v == v) break;//弹栈一直到u-v
- }
- }
- } else if (dfn[v] < dfn[u] && v != fa) {//回边
- s.push((edge) {u, v});
- low[u] = min(low[u], dfn[v]);
- }
- }
- && child == ) cut[u] = ;//把根节点看做普通节点了,所以下面最后的特殊判断必需。
- }
求边双连通分量:
边双联通分量可能简单些,
简单说,就是找到桥(割边)后,把桥删掉,剩下很多连通块,每一个连通块就是一个边双连通分量
因为把图中的桥删掉后,剩下的边都不是桥,既然不是桥,就不能把连通块分成多部分,所以剩下的连通块满足边双连通的定义,所以剩余的连通块都是边双联通
桥不属于任何一个边双连通分量,其余的边和每个顶点,都属于且只属于一个边连通分量
所以我们要做的就只有求桥,删边,染色DFS
code:
- ;
- ];
- void tarjan(int x, int in_edge) {
- dfn[x] = low[x] = ++cnt;
- for (int i = head[x]; ~i; i = e[i].nx) {
- int y = e[i].y;
- if (!dfn[y]) {
- tarjan(y, i);
- low[x] = min(low[x], low[y]);
- ] = ;
- }
- ))
- low[x] = min(low[x], dfn[y]);
- }
- }
- ;
- void dfs(int x, int color) {
- block[x] = color;
- ; i = e[i].nx) {
- int y = e[i].y;
- if (cut[i]) continue;
- if (!block[y]) dfs(y, color);
- }
- }
- void get_edccs() {
- ; i <= n; i++)
- if (!block[i])
- dfs(i, ++dcc);
- }
例题:
Tarjan应用之——LCA
既然说到了Tarjan,那就不得不说一下它的应用,求LCA
Tarjan求LCA是一个O(n+m)的离线做法,查询是O(1)的
Tarjan求LCA其实就是深搜+并查集
先把算法流程摆上
1、任意选一个点为根节点
2、把当前节点u的father设为u,看与u相邻的点v,若v没被搜过,深搜v,深搜完后,把father[v]设成u
3、搜完一个点u后,开始判断与它相关的询问,如果另一个点被搜过,LCA就是另一个点的祖先
我相信看完了我的描述之后,你们还是不知道什么意思
那我们还是手模一下它的流程:
假设我们有这么一张图:
我们要查询2-4,5-6,4-7,6-8的LCA
从1开始搜索,fa[1] = 1,vis[1] = 1
搜到2,fa[2] = 2,vis[2] = 1
搜到3,fa[3] = 3,vis[3] = 1;
搜到4,fa[4] = 4,vis[4] = 1;
搜到6,fa[6] = 6,vis[6] = 1;
到此为止,我们就得到了这样的图
发现6没有别的连边了,开始回溯
我们看到有一个关于6的询问6-8,发现vis[8] = 0,不管
回溯到4,fa[6]=4,发现和4有关的询问2-4,于是lca(2,4) = find(2) = 2 ,记录一下//find是找祖宗函数,并查集里常用
回溯到3,发现还有一条边,继续搜
搜到5,fa[5] = 5,vis[5] = 1
5没有别的连边,找和5相关的询问5-6,lca(5,6) = find(6) = 3,记录一下
回溯到3,fa[5] = 3
回溯到2,fa[3] = 2
回溯到1,fa[2] = 1
这样左边的就搜完了
得到
回溯到1
继续搜,搜到7,fa[7] = 7,vis[7] = 1
搜到8,fa[8] = 8,vis[8] = 1;
8没有其他的连边,发现有一个和8相关的询问6-8,lca(6,8) = find(6) = 1
回溯到7,fa[8] = 7,发现一个和7相关的询问4-7,lca(4,7) = find(4) = 1
回溯到1,fa[7] = 1
到此为止,我们已经把整棵树dfs了一遍,也处理处了所有的询问的结果
最后直接输出就好了
看代码:
- //e是记录原图,_e是记录询问
- //lca记录的是第i组询问的结果
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n, m, k, num, _num;
- int fa[N], head[N], _head[N];
- bool vis[N];
- struct node {
- int v, nx, lca;
- } e[N], _e[N];
- template<class T>inline void read(T &x) {
- x = ; ; char ch = getchar();
- while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
- + ch - ', ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u, int v) {
- e[++num].nx = head[u], e[num].v = v, head[u] = num;
- }
- inline void _add(int u, int v) {
- _e[++_num].nx = _head[u], _e[_num].v = v, _head[u] = _num;
- }
- int find(int x) {
- return x == fa[x] ? x : fa[x] = find(fa[x]);
- }
- void tarjan(int u) {
- fa[u] = u, vis[u] = ;
- for (int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if (!vis[v]) {
- tarjan(v);
- fa[v] = u;
- }
- }
- for (int i = _head[u]; ~i; i = _e[i].nx) {
- int v = _e[i].v;
- if (vis[v]) {
- _e[i].lca = find(v);
- ) _e[i + ].lca = _e[i].lca;//因为是加的双向边,奇数边是从父到子加的,偶数边是从子到父加的,两者相同
- ].lca = _e[i].lca;
- }
- }
- }
- int main(int argc, char const *argv[]) {
- memset(head, -, sizeof(head));
- memset(_head, -, sizeof(_head));
- read(n), read(m), read(k);
- , x, y; i < n; ++ i) read(x), read(y), add(x, y), add(y, x);
- , x, y; i <= m; ++ i) read(x), read(y), _add(x, y), _add(y, x);
- tarjan(k);
- ; i <= m; ++ i) printf(].lca);
- ;
- }
例题:
P2912 [USACO08OCT]牧场散步Pasture Walking
- //dis[i]表示到i的距离
- //这一类题,dis直接在Tarjan中计算就好了
- #include <bits/stdc++.h>
- using namespace std;
- ;
- int n, m, num, _num;
- int head[N], _head[N], fa[N], dis[N];
- bool vis[N];
- struct node {
- int v, nx, w, lca, ans;
- } e[N], _e[N];
- template<class T>inline void read(T &x) {
- x = ; ; char ch = getchar();
- while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
- + ch - ', ch = getchar();
- x = f ? -x : x;
- return ;
- }
- inline void add(int u, int v, int w) {
- e[++num].nx = head[u], e[num].v = v, e[num].w = w, head[u] = num;
- }
- inline void _add(int u, int v) {
- _e[++_num].nx = _head[u], _e[_num].v = v, _head[u] = _num;
- }
- int find(int x) {
- return x == fa[x] ? x : fa[x] = find(fa[x]);
- }
- void tarjan(int u) {
- fa[u] = u, vis[u] = ;
- for (int i = head[u]; ~i; i = e[i].nx) {
- int v = e[i].v;
- if (!vis[v]) {
- dis[v] = dis[u] + e[i].w;
- tarjan(v);
- fa[v] = u;
- }
- }
- for (int i = _head[u]; ~i; i = _e[i].nx) {
- int v = _e[i].v;
- if (vis[v]) {
- _e[i].lca = find(v);
- _e[i].ans = dis[u] + dis[v] - dis[_e[i].lca] * ;
- ) _e[i + ].lca = _e[i].lca, _e[i + ].ans = _e[i].ans;
- ].lca = _e[i].lca, _e[i - ].ans = _e[i].ans;
- }
- }
- }
- int main(int argc, char const *argv[]) {
- memset(head, -, sizeof(head));
- memset(_head, -, sizeof(_head));
- read(n), read(m);
- , x, y, z; i < n; ++ i) read(x), read(y), read(z), add(x, y, z), add(y, x, z);
- , x, y; i <= m; ++ i) read(x), read(y), _add(x, y), _add(y, x);
- tarjan();
- ; i <= m; ++ i) printf(].ans);
- ;
- }
P2912
一般的LCA都可以做的吧。。(我做题少,而且都是用倍增做的,如有什么不可以题的话还请告知我一下)
Tarjan总结(缩点+割点(边)+双联通+LCA+相关模板)的更多相关文章
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- Tarjan的缩点&&割点概述
What is Tarjan? Tarjan,是一种用来解决图的联通性的一种有效途径,它的一般俗称叫做:缩点.我们首先来设想一下: 如果我们有一个图,其中A,B,C构成一个环,那么我们在某种条件下,如 ...
- hdu 2460 poj 3694 (双联通+LCA)
在给出的两个点上加一条边,求剩下桥的数量,,不会LCA在线,就用了最普通的,先Tarjan双联通缩点,然后将缩完的图建成一棵树,树的所有边就是桥了,如果在任意两点间加一条边的话,那么从两点到最近公共祖 ...
- Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】
一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...
- (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)
基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...
- 无向连通图求割点(tarjan算法去掉改割点剩下的联通分量数目)
poj2117 Electricity Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 3603 Accepted: 12 ...
- POJ 3694 Network 无向图双联通+LCA
一开始题目没看清楚,以为是增加那条边后还有多少桥,所以就当做是无向图tarjan缩点后建树,然后求u,v的最近公共祖先,一直wa. 后来再看题目后才发现边放上去后不会拿下来了,即增加i条边后桥的数量. ...
- HDU4612 Warm up —— 边双联通分量 + 重边 + 缩点 + 树上最长路
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=4612 Warm up Time Limit: 10000/5000 MS (Java/Ot ...
- POJ3177 Redundant Paths —— 边双联通分量 + 缩点
题目链接:http://poj.org/problem?id=3177 Redundant Paths Time Limit: 1000MS Memory Limit: 65536K Total ...
随机推荐
- SDN 实验室学生们
SDN实验室系列:https://edu.cnblogs.com/campus/fzu/SdnLab --研究生及本科生们的博客及GitHub链接.欢迎各位大佬招募. 研究生 2018级 姓名 博客地 ...
- Day12 Python基础之生成器、迭代器(高级函数)(十)
https://www.cnblogs.com/yuanchenqi/articles/5769491.html 1. 列表生成式 我现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7 ...
- Linux awk使用方法~~整理
目录 awk行处理方式 awk命令格式 命令行格式 脚本格式 命令行格式——基本格式 awk内置变量 awk内置函数 测试数据 awk变量和函数使用实例 逻辑判断式 扩展格式 BEGIN 和 END ...
- 我的第一个Go web程序 纪念一下
参考Go web编程,很简单的程序: 大致的步骤: 绑定ip和端口 绑定对应的处理器或者处理器函数,有下面两种选择,选择一种即可监听ip及端口 处理器: 定义一个struct结构体 然后让这个结构体实 ...
- shell脚本--php执行普通shell命令
这里只演示一些普通的shell命令,一些需要root用户权限执行的命令,请参考:php以root权限执行shell命令 php执行shell命令,可以使用下面几个函数: string system ( ...
- pip Read timed out 和 pip 源
解决方法,设置超时时间 pip --default-timeout=100 install -U Pillow 安装时指定源(--index-url) #例如安装scipy时使用豆瓣的源 pip in ...
- Nginx三部曲(1)基础
我们会告诉你 Nginx 是如何工作的,其背后的概念有哪些,以及如何优化它以提升应用程序的性能.还会告诉你如何安装,如何启动.运行. 这个教程包括三节: 基础概念——你可以了解命令(directive ...
- ES7的新特性
ES7的新特性 ES7 特性: 1.Array.prototype.includes2.Exponentiation Operator(求幂运算) 一,Array.prototype.includes ...
- 在linux和本地系统之间进行数据传输的简单方法--lrzsz
lrzsz是一款在linux里可代替ftp上传和下载的程序. >>提君博客原创 http://www.cnblogs.com/tijun/ << 提君博客原创 安装和使用非 ...
- Flutter之Container详解
1 基本内容1.1 继续关系Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget &g ...